PopupWindow background sometimes gets transparent and purple - android

Here's how I create a PopupWindow:
private static PopupWindow createPopup(FragmentActivity activity, View view)
{
PopupWindow popup = new PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
popup.setOutsideTouchable(true);
popup.setFocusable(true);
popup.setBackgroundDrawable(new ColorDrawable(Tools.getThemeReference(activity, R.attr.main_background_color)));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
popup.setElevation(Tools.convertDpToPixel(8, activity));
PopupWindowCompat.setOverlapAnchor(popup, true);
return popup;
}
main_background_color is a solid color, white or black, depending on the theme. Sometimes following happens:
How can I avoid this? It happens in the emulator with android 6 SOMETIMES only for example... Normally, the PopupWindow background works as expected though...
Edit
Additionally, here's my getThemeReference method:
public static int getThemeReference(Context context, int attribute)
{
TypedValue typeValue = new TypedValue();
context.getTheme().resolveAttribute(attribute, typeValue, false);
if (typeValue.type == TypedValue.TYPE_REFERENCE)
{
int ref = typeValue.data;
return ref;
}
else
{
return -1;
}
}
EDIT 2 - this may solve the problem: using getThemeColor instead of getThemeReference
public static int getThemeColor(Context context, int attribute)
{
TypedValue typeValue = new TypedValue();
context.getTheme().resolveAttribute(attribute, typeValue, true);
if (typeValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typeValue.type <= TypedValue.TYPE_LAST_COLOR_INT)
{
int color = typeValue.data;
return color;
}
else
{
return -1;
}
}

Thanks for the update, I asked you to show the method because I actually use the same kind of thing in my apps to retrieve color attributes, but our methods are a bit different.
Here is mine:
public static int getThemeColor(Context context, int attributeId) {
TypedValue typedValue = new TypedValue();
TypedArray a = context.obtainStyledAttributes(typedValue.data, new int[] { attributeId });
int color = a.getColor(0, 0);
a.recycle();
return color;
}
Even though I can't be sure it is indeed the problem, there is something wrong with your comments. The call new ColorDrawable() is expecting a color, not a reference. I sometimes also did this mistake in the past and also got weird colors, because the system was trying to generate a color with a reference ID. Have you tried a real color like red to see if your method really works?
I would replace anyway your method with mine because it guarantees you to retrieve a color.

Related

Custom passwordToggleDrawable is too large in TextInputLayout

I have used android.support.design.widget.TextInputLayout to make a password input that allows the user to toggle readability on the password. The xml is as follows:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:hintEnabled="false"
app:passwordToggleDrawable="#drawable/password_toggle_selector"
app:passwordToggleEnabled="true" >
<android.support.design.widget.TextInputEditText
android:id="#+id/password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Password"
android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout>
The drawable selector is as described by How to customize android passwordToggleDrawable
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/password_toggle_show"
android:state_checked="true"/>
<item android:drawable="#drawable/password_toggle_hide"/>
</selector>
The issue is that the custom drawable becomes really large. Not larger than the edittext, but rather it seems to maximize its size while still fitting inside it (so, it seems to be bounded by the height of the element). However, if I leave the passwordToggleDrawable property unset, the drawable for the toggle is sized as is normal for android (I am sure you have seen the icon in other apps before). After much searching I have found a way to resize the custom one, but I am not happy with how its done (requires 2 extra xml files per drawable) and it only works for API 23+.
I would like to know if there is a good way to set the size of the drawable, or better yet, make it target the size of the default drawable?
I have tried setting the padding of the EditText as the source of TextInputLayout says that it gets the four paddings from it and apply to the mPasswordToggleView (line 1143), but it made no change on the icon and (as expected) also affected the padding of the EditText. I have tried setting minheight to 0. I have also tried changing between EditText and TextInputEditText (using the latter now as it seems to be recommended). I have tried switching the layout_height properties to wrap_content. I have tried scaling the drawable using xml's <scale> tag with the scale properties set. I have tried similarly with the <inset> tag. But none of those methods works.
The way I found (and am currently using) to resize the drawable that actually works is by using the xml tag <layer-list>, while setting the width and height properties. Then the <selector> xml file references those resized drawables instead of the png ones. But I don't like this solution because as I mentioned it requires API 23 and because of that results in a total of 4 extra xml files. It also sets the width and height by themselves, instead of keeping the ratio locked.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="#drawable/password_toggle_hide"
android:width="22dp"
android:height="15dp"/>
</layer-list>
TL;DR
How do I set the size of a custom passwordToggleDrawable in TextInputLayout? Preferably to same size as the default drawable.
I know this is an old question, but I faced the same problem and I believe I figure out a simple solution for this.
I'm using the TextInputLayout for the newest material library, and the only thing that I did was to find the reference for the endIcon from the TextInputLayout and change it's minimum dimensions.
val dimension = //here you get the dimension you want to
val endIconImageView = yourTextInputLayout.findViewById<ImageView>(R.id.text_input_end_icon)
endIconImageView.minimumHeight = dimension
endIconImageView.minimumWidth = dimension
yourTextInputLayout.requestLayout()
Important things to notice:
I did this on the OnFinishedInflated from a custom TextInputLayout, but I believe it will work fine on some activity class.
Cheers!
I face same problem. To avoid this situation I used png and set them based dpi like drawable-hdpi, drawable-mdpi etc. Also make those drawable as per radio. Hope that this tricks also work for you.
I were unable to find any solution to the question I actually asked, but I decided to instead solve the issue by disregarding the "in InputTextLayout" part of the question and implemented my own version of the class.
Mostly it is just a copy of InputTextLayout (sadly that class doesnt translate well for subclassing as everything is private) but with most of the stuff I dont need removed, and more importantly, with the CheckableImageButton mPasswordToggleView changed to a ViewGroup containing a View.
The ViewGroup is the clickable button, and handles setMinimumDimensions to keep the clickable area at min 48 dp, like the original did through design_text_input_password_icon.xml. This also makes small drawables not hug the right side of the screen as they are centered in the clickable area, giving the margin that the default drawable appears to have.
The View (or more precisely, a new subclass of it I called CheckableView) is the actual drawable (setBackground()), replacing the CheckableImageButton as the container of the drawable that lets it switch based on state_checked selector.
The xml-property passwordToggleSize allows a dimension to be set, which is used to scale the drawable. I opted to only have one value instead of width&height, and the drawable scales with its ratio locked such that its greatest dimension matches the dimension specified. I made the default size 24dp, as is specified for the default-drawable in design_ic_visibility.xml.
PasswordToggleLayout.java:
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.AbsSavedState;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.TextViewCompat;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.mylifediary.android.client.R;
public class PasswordToggleLayout extends LinearLayout {
// Default values from InputTextLayout's drawable and inflated layout
final int BUTTON_MIN_SIZE = 48; // The button is 48 dp at minimum.
final int DEFAULT_DRAWABLE_SIZE = 24; // The default drawable is 24 dp.
int mButtonMinSize;
final FrameLayout mInputFrame;
EditText mEditText;
private boolean mPasswordToggleEnabled;
private Drawable mPasswordToggleDrawable;
private CharSequence mPasswordToggleContentDesc;
ViewGroup mPasswordToggleViewGroup;
CheckableView mPasswordToggleView;
private boolean mPasswordToggledVisible;
private int mPasswordToggleSize;
private Drawable mPasswordToggleDummyDrawable;
private Drawable mOriginalEditTextEndDrawable;
private ColorStateList mPasswordToggleTintList;
private boolean mHasPasswordToggleTintList;
public PasswordToggleLayout(Context context) {
this(context, null);
}
public PasswordToggleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PasswordToggleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
setWillNotDraw(false);
setAddStatesFromChildren(true);
mButtonMinSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, BUTTON_MIN_SIZE,
getResources().getDisplayMetrics());
mInputFrame = new FrameLayout(context);
mInputFrame.setAddStatesFromChildren(true);
addView(mInputFrame);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PasswordToggleLayout, defStyleAttr,
R.style.Widget_Design_TextInputLayout);
mPasswordToggleEnabled = a.getBoolean(
R.styleable.PasswordToggleLayout_passwordToggleEnabled, false);
mPasswordToggleDrawable = a.getDrawable(
R.styleable.PasswordToggleLayout_passwordToggleDrawable);
mPasswordToggleContentDesc = a.getText(
R.styleable.PasswordToggleLayout_passwordToggleContentDescription);
if (a.hasValue(R.styleable.PasswordToggleLayout_passwordToggleTint)) {
mHasPasswordToggleTintList = true;
mPasswordToggleTintList = a.getColorStateList(
R.styleable.PasswordToggleLayout_passwordToggleTint);
}
mPasswordToggleSize = a.getDimensionPixelSize(
R.styleable.PasswordToggleLayout_passwordToggleSize,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_DRAWABLE_SIZE, getResources().getDisplayMetrics()));
a.recycle();
applyPasswordToggleTint();
}
private void setEditText(EditText editText) {
// If we already have an EditText, throw an exception
if (mEditText != null) {
throw new IllegalArgumentException(
"We already have an EditText, can only have one");
}
mEditText = editText;
final boolean hasPasswordTransformation = hasPasswordTransformation();
updatePasswordToggleView();
}
private void updatePasswordToggleView() {
if (mEditText == null) {
// If there is no EditText, there is nothing to update
return;
}
if (shouldShowPasswordIcon()) {
if (mPasswordToggleView == null) {
// Keep ratio
double w = mPasswordToggleDrawable.getIntrinsicWidth();
double h = mPasswordToggleDrawable.getIntrinsicHeight();
double scale = mPasswordToggleSize / Math.max(w,h);
int scaled_width = (int) (w * scale);
int scaled_height = (int) (h * scale);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_VERTICAL | Gravity.END | Gravity.RIGHT);
FrameLayout.LayoutParams lp2 = new FrameLayout.LayoutParams(
scaled_width, scaled_height, Gravity.CENTER);
mPasswordToggleViewGroup = new FrameLayout(this.getContext());
mPasswordToggleViewGroup.setMinimumWidth(mButtonMinSize);
mPasswordToggleViewGroup.setMinimumHeight(mButtonMinSize);
mPasswordToggleViewGroup.setLayoutParams(lp);
mInputFrame.addView(mPasswordToggleViewGroup);
mPasswordToggleViewGroup.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
passwordVisibilityToggleRequested(false);
}
});
mPasswordToggleView = new CheckableView(this.getContext());
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
mPasswordToggleView.setContentDescription(mPasswordToggleContentDesc);
mPasswordToggleView.setLayoutParams(lp2);
mPasswordToggleViewGroup.addView(mPasswordToggleView);
}
if (mEditText != null && ViewCompat.getMinimumHeight(mEditText) <= 0) {
// We should make sure that the EditText has the same min-height
// as the password toggle view. This ensure focus works properly,
// and there is no visual jump if the password toggle is enabled/disabled.
mEditText.setMinimumHeight(
ViewCompat.getMinimumHeight(mPasswordToggleViewGroup));
}
mPasswordToggleViewGroup.setVisibility(VISIBLE);
mPasswordToggleView.setChecked(mPasswordToggledVisible);
// Need to add a dummy drawable as the end compound drawable so that
// the text is indented and doesn't display below the toggle view.
if (mPasswordToggleDummyDrawable == null) {
mPasswordToggleDummyDrawable = new ColorDrawable();
}
// Important to use mPasswordToggleViewGroup, as mPasswordToggleView
// wouldn't replicate the margin of the default-drawable.
mPasswordToggleDummyDrawable.setBounds(
0, 0, mPasswordToggleViewGroup.getMeasuredWidth(), 1);
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
// Store the user defined end compound drawable so that we can restore it later
if (compounds[2] != mPasswordToggleDummyDrawable) {
mOriginalEditTextEndDrawable = compounds[2];
}
TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0],
compounds[1], mPasswordToggleDummyDrawable, compounds[3]);
// Copy over the EditText's padding so that we match
mPasswordToggleViewGroup.setPadding(mEditText.getPaddingLeft(),
mEditText.getPaddingTop(), mEditText.getPaddingRight(),
mEditText.getPaddingBottom());
} else {
if (mPasswordToggleViewGroup != null
&& mPasswordToggleViewGroup.getVisibility() == VISIBLE) {
mPasswordToggleViewGroup.setVisibility(View.GONE);
}
if (mPasswordToggleDummyDrawable != null) {
// Make sure that we remove the dummy end compound drawable if
// it exists, and then clear it
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
if (compounds[2] == mPasswordToggleDummyDrawable) {
TextViewCompat.setCompoundDrawablesRelative(mEditText,
compounds[0], compounds[1],
mOriginalEditTextEndDrawable, compounds[3]);
mPasswordToggleDummyDrawable = null;
}
}
}
}
private void applyPasswordToggleTint() {
if (mPasswordToggleDrawable != null && mHasPasswordToggleTintList) {
mPasswordToggleDrawable = DrawableCompat.wrap(mPasswordToggleDrawable).mutate();
DrawableCompat.setTintList(mPasswordToggleDrawable, mPasswordToggleTintList);
if (mPasswordToggleView != null
&& mPasswordToggleView.getBackground() != mPasswordToggleDrawable) {
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
}
}
}
private void passwordVisibilityToggleRequested(boolean shouldSkipAnimations) {
if (mPasswordToggleEnabled) {
// Store the current cursor position
final int selection = mEditText.getSelectionEnd();
if (hasPasswordTransformation()) {
mEditText.setTransformationMethod(null);
mPasswordToggledVisible = true;
} else {
mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
mPasswordToggledVisible = false;
}
mPasswordToggleView.setChecked(mPasswordToggledVisible);
if (shouldSkipAnimations) {
mPasswordToggleView.jumpDrawablesToCurrentState();
}
// And restore the cursor position
mEditText.setSelection(selection);
}
}
private boolean hasPasswordTransformation() {
return mEditText != null
&& mEditText.getTransformationMethod() instanceof PasswordTransformationMethod;
}
private boolean shouldShowPasswordIcon() {
return mPasswordToggleEnabled && (hasPasswordTransformation() || mPasswordToggledVisible);
}
#Override
public void addView(View child, int index, final ViewGroup.LayoutParams params) {
if (child instanceof EditText) {
// Make sure that the EditText is vertically at the bottom,
// so that it sits on the EditText's underline
FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params);
flp.gravity = Gravity.CENTER_VERTICAL
| (flp.gravity & ~Gravity.VERTICAL_GRAVITY_MASK);
mInputFrame.addView(child, flp);
// Now use the EditText's LayoutParams as our own and update them
// to make enough space for the label
mInputFrame.setLayoutParams(params);
setEditText((EditText) child);
} else {
// Carry on adding the View...
super.addView(child, index, params);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
updatePasswordToggleView();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.isPasswordToggledVisible = mPasswordToggledVisible;
return ss;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.isPasswordToggledVisible) {
passwordVisibilityToggleRequested(true);
}
requestLayout();
}
static class SavedState extends AbsSavedState {
boolean isPasswordToggledVisible;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel source, ClassLoader loader) {
super(source, loader);
isPasswordToggledVisible = (source.readInt() == 1);
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(isPasswordToggledVisible ? 1 : 0);
}
public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
#Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
#Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in, null);
}
#Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public static class CheckableView extends View {
private final int[] DRAWABLE_STATE_CHECKED =
new int[]{android.R.attr.state_checked};
private boolean mChecked;
public CheckableView(Context context) {
super(context);
}
public CheckableView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public CheckableView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
}
}
#Override
public int[] onCreateDrawableState(int extraSpace) {
if (mChecked) {
return mergeDrawableStates(
super.onCreateDrawableState(extraSpace
+ DRAWABLE_STATE_CHECKED.length), DRAWABLE_STATE_CHECKED);
} else {
return super.onCreateDrawableState(extraSpace);
}
}
}
}
And then in an attrs.xml:
<declare-styleable name="PasswordToggleLayout">
<attr name="passwordToggleEnabled" format="boolean"/>
<attr name="passwordToggleDrawable" format="reference"/>
<attr name="passwordToggleContentDescription" format="string"/>
<attr name="passwordToggleTint" format="color"/>
<attr name="passwordToggleSize" format="dimension"/>
</declare-styleable>
Same issue for me. The problem comes from the gradle material API implementation:
implementation 'com.google.android.material:material:1.1.0'
downgrade to version 1.0.0 fixes the issue:
implementation 'com.google.android.material:material:1.0.0'

Why can't I set the elevation on a view that I add programmatically?

Disclaimer: I am using Xamarin.Android.
I created a view, set its elevation, and then add it to my main layout. The view successfully gets added to the layout when I trigger the event, but there is no elevation shadow whatsoever.
Here is what I am working with:
View that gets added programmatically:
public class TooltipTest : FrameLayout
{
private Context context;
private ShapeDrawable box;
private View carrot;
private string message;
public TextView TooltipText
{
get;
private set;
}
public TooltipTest(Context context, string message) : base(context)
{
this.context = context;
this.message = message;
Initialize();
}
private void Initialize()
{
CreateText();
}
private void CreateText()
{
int paddingTopBottom = 30;
int paddingLeftRight = 27;
TooltipText = new TextView(context);
TooltipText.Text = message;
TooltipText.SetTextColor(new Color(ContextCompat.GetColor(context, Resource.Color.tooltipText)));
TooltipText.SetTextSize(ComplexUnitType.Sp, 14f);
TooltipText.SetPadding(paddingLeftRight, paddingTopBottom, paddingLeftRight, paddingTopBottom);
TooltipText.SetBackgroundColor(new Color(ContextCompat.GetColor(context, Resource.Color.tooltipBackground)));
AddView(TooltipText);
}
Event to add the view:
ButtonTest.Click += (sender, e) => {
var tooltip = new TooltipTest(this, Resources.GetString(Resource.String.test_text));
var tooltipParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
tooltip.Elevation = 20f;
ParentLayout.AddView(tooltip, tooltipParams);
};
Any ideas on why the shadow doesn't show? I've tried setting SetClipToPadding(false) and SetClipChildren(false) on the tooltip, but that had no effect.
Use the AppCompat method ViewCompat.SetElevation(View, int) to set the elevation as desired. But on a pre-Lollipop devices, the method appears to do nothing.
The only way I found to render shadows to pre-Lollipop UI elements was using a background instead:
android:background="#android:drawable/dialog_holo_light_frame"
If you want do dig more on this topic, go to this reddit topic and search for elevation. There are really good updated information there.
I have discovered why setting the elevation wasn't working on my custom TooltipTest view. The problem was that that view itself didn't have any background set, and according to Android's documentation, there needs to be some sort of resource in the background property, whether it be a color or some drawable.
As you can see from my original post, within my TooltipTest class, which inherits from a FrameLayout, I create a TextView (TooltipText) and add it to the layout. Then, within my Activity class, I set the elevation on that TooltipTest class. Since I didn't explicitly set a Background resource for the TooltipTest Layout class, Android didn't know what to draw a shadow for.
All I had to do to fix my problem was add Elevation to the TooltipText object, not the TooltipTest object.
private void CreateText()
{
int paddingTopBottom = 30;
int paddingLeftRight = 27;
TooltipText = new TextView(context);
TooltipText.Text = message;
TooltipText.SetTextColor(new Color(ContextCompat.GetColor(context, Resource.Color.tooltipText)));
TooltipText.SetTextSize(ComplexUnitType.Sp, 14f);
TooltipText.SetPadding(paddingLeftRight, paddingTopBottom, paddingLeftRight, paddingTopBottom);
TooltipText.SetBackgroundColor(new Color(ContextCompat.GetColor(context, Resource.Color.tooltipBackground)));
TooltipText.Elevation = 21f; //(or whatever value you want)
AddView(TooltipText);
}
If you want a shadow on the TooltipTest class, you would need to set the Background property:
private void CreateText()
{
int paddingTopBottom = 30;
int paddingLeftRight = 27;
TooltipText = new TextView(context);
TooltipText.Text = message;
TooltipText.SetTextColor(new Color(ContextCompat.GetColor(context, Resource.Color.tooltipText)));
TooltipText.SetTextSize(ComplexUnitType.Sp, 14f);
TooltipText.SetPadding(paddingLeftRight, paddingTopBottom, paddingLeftRight, paddingTopBottom);
TooltipText.SetBackgroundColor(new Color(ContextCompat.GetColor(context, Resource.Color.tooltipBackground)));
SetBackgroundColor (new Color (ContextCompat.GetColor (context, Resource.Color.white)));
AddView(TooltipText);
}
Doing it the latter way would give you an ugly white background with a shadow under it. However, you can use any sort of drawable you want for the Background property. Instead of using SetBackgroundColor(Color color), you can do Background = (some drawable);

Get material design colors at runtime outside the scope

I´m trying to outsource my custom view into an own library.
Is it possible to get the colorPrimary, colorPrimaryDark and colorAccent attributes outside the scope, maybe at runtime?
Solution
I´ve pointed it out myself.
In xml use:
?attr/attributeName
f.e.
?attr/colorAccent
In code use sth like:
public int getColorValueByAttr(Context context, int attrResId) {
int color = 0;
TypedArray a = null;
try {
int[] attrs = {attrResId};
a = context.obtainStyledAttributes(attrs);
color = a.getColor(0, 0);
} finally {
if (a != null) {
a.recycle();
}
}
return color;
}

How to get the ActionBar height?

I am trying to get the height of the ActionBar (using Sherlock) every time an activity is created (specially to handle configuration changes on rotation where the ActionBar height might change).
For this I use the method ActionBar.getHeight() which works only when the ActionBar is shown.
When the first activity is created for the first time, I can call getHeight() in the onCreateOptionsMenu callback. But this method is not called after.
So my question is when can I call getHeight() and be assured that it doesn't return 0?
Or if it is not possible, how can I set the height of the ActionBar ?
While #birdy's answer is an option if you want to explicitly control the ActionBar size, there is a way to pull it up without locking the size that I found in support documentation. It's a little awkward but it's worked for me. You'll need a context, this example would be valid in an Activity.
// Calculate ActionBar height
TypedValue tv = new TypedValue();
if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
{
int actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
}
Kotlin:
val tv = TypedValue()
if (requireActivity().theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
val actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, resources.displayMetrics)
}
In XML, you should use this attribute:
android:paddingTop="?android:attr/actionBarSize"
Ok I was googling around and get to this post several times so I think it'll be good to be described not only for Sherlock but for appcompat also:
Action Bar height using appCompat
Pretty similiar to #Anthony description you could find it with following code(I've just changed resource id):
TypedValue tv = new TypedValue();
if (getActivity().getTheme().resolveAttribute(R.attr.actionBarSize, tv, true))
{
int actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
}
Pre Sandwitch Problem
I needed action bar height because on pre ICE_CREAM_SANDWITCH devices my view was overlaping action bar. I tried setting android:layout_marginTop="?android:attr/actionBarSize", android:layout_marginTop="?attr/actionBarSize", setting overlap off to view/actionbar and even fixed actionbar height with custom theme but nothing worked. That's how it looked:
Unfortunately I found out that with method described above I get only half of the height(I assume that options bar is not taken in place) so to completely fix the isue I need to double action bar height and everything seems ok:
If someone knows better solution(than doubling actionBarHeight) I'll be glad to let me know as I come from iOS development and I found most of Android view's stuff pretty confusing :)
Regards,
hris.to
#Anthony answer works for devices that support ActionBar and for those devices which support only Sherlock Action Bar following method must be used
TypedValue tv = new TypedValue();
if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
if(actionBarHeight ==0 && getTheme().resolveAttribute(com.actionbarsherlock.R.attr.actionBarSize, tv, true)){
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
}
//OR as stated by #Marina.Eariel
TypedValue tv = new TypedValue();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){
if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
}else if(getTheme().resolveAttribute(com.actionbarsherlock.R.attr.actionBarSize, tv, true){
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
}
i think the safest way would be :
private int getActionBarHeight() {
int actionBarHeight = getSupportActionBar().getHeight();
if (actionBarHeight != 0)
return actionBarHeight;
final TypedValue tv = new TypedValue();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
} else if (getTheme().resolveAttribute(com.actionbarsherlock.R.attr.actionBarSize, tv, true))
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
return actionBarHeight;
}
To set the height of ActionBar you can create new Theme like this one:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.FixedSize" parent="Theme.Sherlock.Light.DarkActionBar">
<item name="actionBarSize">48dip</item>
<item name="android:actionBarSize">48dip</item>
</style>
</resources>
and set this Theme to your Activity:
android:theme="#style/Theme.FixedSize"
If you are using a Toolbar as the ActionBar,
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
then just use the layout_height of the toolbar.
int actionBarHeight = toolbar.getLayoutParams().height;
Java:
int actionBarHeight;
int[] abSzAttr;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
abSzAttr = new int[] { android.R.attr.actionBarSize };
} else {
abSzAttr = new int[] { R.attr.actionBarSize };
}
TypedArray a = obtainStyledAttributes(abSzAttr);
actionBarHeight = a.getDimensionPixelSize(0, -1);
xml:
?attr/actionBarSize
If you're using Xamarin/C#, this is the code you're looking for:
var styledAttributes = Context.Theme.ObtainStyledAttributes(new[] { Android.Resource.Attribute.ActionBarSize });
var actionBarSize = (int)styledAttributes.GetDimension(0, 0);
styledAttributes.Recycle();
you can use this function to get actionbar's height.
public int getActionBarHeight() {
final TypedArray ta = getContext().getTheme().obtainStyledAttributes(
new int[] {android.R.attr.actionBarSize});
int actionBarHeight = (int) ta.getDimension(0, 0);
return actionBarHeight;
}
A ready method to fulfill the most popular answer:
public static int getActionBarHeight(
Activity activity) {
int actionBarHeight = 0;
TypedValue typedValue = new TypedValue();
try {
if (activity
.getTheme()
.resolveAttribute(
android.R.attr.actionBarSize,
typedValue,
true)) {
actionBarHeight =
TypedValue.complexToDimensionPixelSize(
typedValue.data,
activity
.getResources()
.getDisplayMetrics());
}
} catch (Exception ignore) {
}
return actionBarHeight;
}
Here's an updated version with Kotlin I used assuming phone is higher than Honeycomb:
val actionBarHeight = with(TypedValue().also {context.theme.resolveAttribute(android.R.attr.actionBarSize, it, true)}) {
TypedValue.complexToDimensionPixelSize(this.data, resources.displayMetrics)
}
This helper method should come in handy for someone. Example: int actionBarSize = getThemeAttributeDimensionSize(this, android.R.attr.actionBarSize);
public static int getThemeAttributeDimensionSize(Context context, int attr)
{
TypedArray a = null;
try{
a = context.getTheme().obtainStyledAttributes(new int[] { attr });
return a.getDimensionPixelSize(0, 0);
}finally{
if(a != null){
a.recycle();
}
}
}
you just have to add this.
public int getActionBarHeight() {
int height;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
height = getActivity().getActionBar().getHeight();
} else {
height = ((ActionBarActivity) getActivity()).getSupportActionBar().getHeight();
}
return height;
}
I found a more generic way to discover it:
int[] location = new int[2];
mainView.getLocationOnScreen(location);
int toolbarHeight = location[1];
where 'mainView' is the root view of your layout.
The idea is basically get the Y position of the mainView as it is set right below the ActionBar (or Toolbar).
The action bar height differs according to the applied theme.
If you're using AppCompatActivity the height will be different in most of cases from the normal Activity.
If you're using AppCompatActivity you should use R.attr.actionBarSize instead of android.R.attr.actionBarSize
public static int getActionBarHeight(Activity activity) {
TypedValue typedValue = new TypedValue();
int attributeResourceId = android.R.attr.actionBarSize;
if (activity instanceof AppCompatActivity) {
attributeResourceId = R.attr.actionBarSize;
}
if (activity.getTheme().resolveAttribute(attributeResourceId, typedValue, true)) {
return TypedValue.complexToDimensionPixelSize(typedValue.data, activity.getResources().getDisplayMetrics());
}
return (int) Math.floor(activity.getResources()
.getDimension(R.dimen.my_default_value));
}
In xml, you can use ?attr/actionBarSize, but if you need access to that value in Java you need to use below code:
public int getActionBarHeight() {
int actionBarHeight = 0;
TypedValue tv = new TypedValue();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv,
true))
actionBarHeight = TypedValue.complexToDimensionPixelSize(
tv.data, getResources().getDisplayMetrics());
} else {
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,
getResources().getDisplayMetrics());
}
return actionBarHeight;
}
The action bar now enhanced to app bar.So you have to add conditional check for getting height of action bar.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
height = getActivity().getActionBar().getHeight();
} else {
height = ((ActionBarActivity) getActivity()).getSupportActionBar().getHeight();
}
A simple way to find actionbar height is from Activity onPrepareOptionMenu method.
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
....
int actionBarHeight = getActionBar().getHeight());
.....
}
In javafx (using Gluon), the height is set at runtime or something like it. While being able to get the width just like normal...
double width = appBar.getWidth();
You have to create a listener:
GluonApplication application = this;
application.getAppBar().heightProperty().addListener(new ChangeListener(){
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
// Take most recent value given here
application.getAppBar.heightProperty.get()
}
});
...And take the most recent value it changed to. To get the height.
After trying everything that's out there without success, I found out, by accident, a functional and very easy way to get the action bar's default height.
Only tested in API 25 and 24
C#
Resources.GetDimensionPixelSize(Resource.Dimension.abc_action_bar_default_height_material);
Java
getResources().getDimensionPixelSize(R.dimen.abc_action_bar_default_height_material);

Android why is preference changing positions automatically?

I have a preferences.xml file with some checkbox preferences, a custom time picker preference, and a custom SeekBar preference. Everything works fine in my 2.2 emulator. I've tried running it in a 1.6 emulator and every time I adjust one of the sliders(SeekBar preferences), or change another preference, the SeekBar preferences change positions. Its like they just shuffle around. Anybody know how I can stop this?
This keeps me from having groupings because they are always changing positions.
I have this for my preference activity:
public class MySettings extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
This for my slider:
public class PreferenceSlider extends Preference implements OnSeekBarChangeListener
{
public static int maximum = 100;
public static int interval = 1;
private float oldValue = 50;
private TextView monitorBox;
private float beforeTouch = 50;
public PreferenceSlider(Context context)
{
super(context);
}
public PreferenceSlider(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public PreferenceSlider(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
protected View onCreateView(ViewGroup parent)
{
LinearLayout layout = new LinearLayout(getContext());
LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT);
params1.gravity = Gravity.LEFT;
params1.weight = 1.0f;
LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT);
params2.gravity = Gravity.LEFT;
LinearLayout.LayoutParams params3 = new LinearLayout.LayoutParams(30, LinearLayout.LayoutParams.WRAP_CONTENT);
params3.gravity = Gravity.LEFT;
layout.setPadding(15, 5, 10, 5);
layout.setOrientation(LinearLayout.VERTICAL);
TextView view = new TextView(getContext());
view.setText(getTitle());
view.setTextSize(18);
view.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD);
view.setGravity(Gravity.LEFT);
view.setLayoutParams(params1);
SeekBar bar = new SeekBar(getContext());
bar.setMax(maximum);
bar.setProgress((int)this.oldValue);
//bar.
//bar.setLayoutParams(params2);
bar.setOnSeekBarChangeListener(this);
this.monitorBox = new TextView(getContext());
this.monitorBox.setTextSize(12);
this.monitorBox.setTypeface(Typeface.MONOSPACE, Typeface.ITALIC);
this.monitorBox.setLayoutParams(params3);
this.monitorBox.setPadding(2, 5, 0, 0);
this.monitorBox.setText(bar.getProgress()+"");
layout.addView(view);
layout.addView(bar);
layout.addView(this.monitorBox);
layout.setId(android.R.id.widget_frame);
return layout;
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,boolean fromTouch)
{
progress = Math.round(((float)progress)/interval)*interval;
if(!callChangeListener(progress)){
seekBar.setProgress((int)this.oldValue);
return; }
seekBar.setProgress(progress);
this.oldValue = progress;
this.monitorBox.setText(progress+"");
updatePreference(progress);
//notifyChanged();
}
#Override
public void onStartTrackingTouch(SeekBar seekBar)
{
beforeTouch = seekBar.getProgress();
}
#Override
public void onStopTrackingTouch(SeekBar seekBar)
{
if(beforeTouch != seekBar.getProgress())
{
notifyChanged();
}
}
#Override
protected Object onGetDefaultValue(TypedArray ta,int index)
{
int dValue = (int)ta.getInt(index,50);
return validateValue(dValue);
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue)
{
int temp = restoreValue ? getPersistedInt(50) : (Integer)defaultValue;
if(!restoreValue)
persistInt(temp);
this.oldValue = temp;
}
private int validateValue(int value)
{
if(value > maximum)
value = maximum;
else if(value < 0)
value = 0;
else if(value % interval != 0)
value = Math.round(((float)value)/interval)*interval;
return value;
}
private void updatePreference(int newValue)
{
SharedPreferences.Editor editor = getEditor();
editor.putInt(getKey(), newValue);
editor.commit(); }
}
Then the preferences.xml is like this:
<com.myprogram.PreferenceSlider
android:key="ringerVolume"
android:defaultValue="50"
android:title="Ringer Volume"/>
<com.myprogram.PreferenceSlider
android:key="notificationVolume"
android:defaultValue="50"
android:title="Notification Volume"/>
You shouldnt worry about android 1.6, at least that this means that you have some dirt in your code, that is not visible for any reason in 2.2 but could be in some other versions, even newer one.
Anyways, most important here, i see that this is a question from 2011 but seems to be reopened now, and at this point, i wouldn't go on with an activity based in this code, that is quite obsolete, so having that besides it is not working perfectly, now it is the best moment, for some re-factoring.
As you can see here addPreferencesFromResource is deprecated since API level 11, that means that 75% of devices look at it like as some old stuff, and some newest devices could one day even raise an error in a sort of "I told you" basis.
There is not alternative method, since what android team wants here, a couple years ago, is not a change in the function, but a fully different approach, using Fragments.
Specifically, you are expected to create some PreferenceFragment objects to load your preferences from a resource file. I would recommend to do it, and don't waste time trying to fix the possible current errors on the activity. Migrating to the new approach will be easy, and you will get a much better result.
To get started, you can read some code here:
Android Developer - Reference - PreferenceFragment
Or read a complete guide here:
Android Developer - Api Guides - UI - Settings
If you go on with this, and have any further problem, don't hesitate to come back with any question and i will be glad to help you out!

Categories

Resources