Can't understand Android custom drawable state - android

I'm new in Android development and I'm writing a small app to understand how it works. I've got all working, but at the moment I can't get a point about custom drawable states... let me explain with some sample code.
Here is my attrs.xml, in which I declare a attribute with name "oddMonth", which is boolean:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DayView">
<attr name="oddMonth" format="boolean"/>
</declare-styleable>
</resources>
Then I have a custom View:
<?xml version="1.0" encoding="utf-8"?>
<com.example.calendar.DayView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="#drawable/dayview_state" >
<TextView android:id="#+id/day_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:paddingRight="3dp" />
</com.example.calendar.DayView>
So I put the line "android:background="#drawable/dayview_state"", which refers to file dayview_state.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:easycalendar="http://schemas.android.com/apk/res/com.example.calendar">
<item easycalendar:oddMonth ="true" android:drawable="#drawable/customborder_odd" />
<item easycalendar:oddMonth ="false" android:drawable="#drawable/customborder_even"/>
</selector>
So far... for what I can understand.... I have a attribute defined in attrs.xml. This attribute represents the state for my custom view. According to the boolean value of this attribute my app will load one of two different xml (that are not important here), each of one defines a different drawable. So the final step is to build my custom class! Follows a extract from the class:
public class DayView extends RelativeLayout {
private static final int[] STATE_ODD_MONTH = { R.attr.oddMonth };
private boolean mOddmonth = true;
public DayView(Context mContext, AttributeSet attrs) {
super(mContext, attrs);
}
#Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mOddmonth) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
mergeDrawableStates(drawableState, STATE_ODD_MONTH);
return drawableState;
} else {
return super.onCreateDrawableState(extraSpace);
}
}
public boolean isOddMonth() {
return mOddmonth;
}
public void setOddMonth(boolean oddMonth) {
if (mOddmonth != oddMonth) {
mOddmonth = oddMonth;
refreshDrawableState();
}
}
}
Ok... so I have here a private variable mOddMonth, whith getter and setter. The constructor which is used to inflate this view elsewhere. Another private variable:
private static final int[] STATE_ODD_MONTH = { R.attr.oddMonth };
which is a array made up of only one int value, that is a reference to the attribute oddMonth defined in attrs.xml. And the inherited method:
#Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mOddmonth) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
mergeDrawableStates(drawableState, STATE_ODD_MONTH);
return drawableState;
} else {
return super.onCreateDrawableState(extraSpace);
}
}
which I can't really "deeply" understand... well, it seems to me that I add a state if the local variable mOddMonth is true, otherwise not. So... my code works only if I replace my dayview_state.xml with the following:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:easycalendar="http://schemas.android.com/apk/res/com.example.calendar">
<item easycalendar:oddMonth ="true" android:drawable="#drawable/customborder_odd" />
<item android:drawable="#drawable/customborder_even"/>
</selector>
In this way the first layout is loaded if THERE IS the state, otherwise will be loaded the second one. But WHAT ABOUT THE VALUE of the state? Nowhere in my code I set the value for this variable/attribute.... where I'm wrong?

I would recommend you reword your question b/c it wasn't clear what you were asking until I read your comment to #kcoppock's answer, which is -
"what i want to do (or I think I should do) is to set this value
somewhere in code according to the actual status of my custom view,
and then force it to render again.... Or I shouldn't?"
At any point, you can query the view to get it drawable state using View.getDrawableState.
If based on this, you want to re-render your drawable, then you have several options.
First of all you can call Drawable.invalidateSelf. But you rarely need to do that because usually your drawable is set as a view's background drawable which is automatically drawn for you in the draw method (not onDraw, which is what you draw). So all you need to do in that case is to invalidate the view (view.invalidate), it will automatically redraw your background drawable (hence picking up your drawable state change).
If you are using your drawable not as a background but for your main drawing then you draw your drawables in onDraw. A simple myDrawable.draw(canvas) should be enough. But remember to vall view.invalidate to trigger the onDraw method.

You're correct; you'll need to assign that value in your constructor with the AttributeSet variable:
TypedArray values = context.obtainStyledAttributes(attrs, STATE_ODD_MONTH);
boolean isOddMonth = values.getBoolean(R.attr.oddMonth, false);
mOddmonth = isOddMonth;
values.recycle();
I believe this should do the trick. I usually use a declare-styleable tag in attrs.xml instead of hardcoding an int[], but I believe it should work identically.

Related

How to set view attribute from custom attr on a custom Preference class?

I have created a class CustomPreference, which inherits from the androidx Preference class, for the purpose of setting a custom layout made of my own components. And I defined a styleable attr on that custom Preference class. But now I'm trying to understand how to pass along the value from that custom styleable attr on to the attribute on my underlying SpecialCustomComponent.
CustomPreference.java
public class CustomPreference extends Preference {
private Context context;
private AttributeSet attrs;
public CustomPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.context = context;
this.attrs = attrs;
setLayoutResource(R.layout.preference_widget_custom);
}
// other required constructor overloads omitted
#Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
// calling this method here results in an error because
// a.getResourceId(R.styleable.PreferenceWidgetCustom_preferenceTitle, 0)
// returns no resource Id at this point
setPreferenceTitle(holder);
}
#Override
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
super.onAttachedToHierarchy(preferenceManager);
this.preferenceManager = preferenceManager;
// alternatively, calling this method here would result in a
// different error, since I don't have access to the viewHolder
// here. To make this work, I tried preserving "viewHolder" in a
// field inside onBindViewHolder (which I don't think I'm
// supposed to do anyways), but the viewHolder reference was
// null, so it didn't work.
setPreferenceTitle();
}
// This is my incorrect attempt at setting "myText" to the value of "preferenceTitle"
private void setPreferenceTitle(PreferenceViewHolder holder) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PreferenceWidgetSwitchCustom, 0, 0);
try {
int preferenceTitleId = a
.getResourceId(R.styleable.PreferenceWidgetCustom_preferenceTitle, 0);
((SpecialCustomComponent) holder.itemView).setText(a.getResources().getString(preferenceTitleId));
}
finally {
a.recycle();
}
}
}
I declared a custom attribute for my CustomPreference in attrs.xml that I am hoping to use to set the underlying "myText" attribute of my custom component in the layout.
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PreferenceWidgetCustom">
<attr name="preferenceTitle" format="string" />
</declare-styleable>
</resources>
The layout for each CustomPreference is defined here in preference_widget_custom.xml. Notice the attribute "myText", which is what I would like to set to the value of "preferenceTitle" defined above.
preference_widget_custom.xml
<?xml version="1.0" encoding="utf-8"?>
<SpecialCustomComponent
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:myText="this is the field I would like to be set by 'preferenceTitle'" />
I am using these preferences in a PreferenceScreen layout like this:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="Settings">
<CustomPreference
android:key="pref_one"
app:preferenceTitle="Preference #1"/>
<CustomPreference
android:key="pref_two"
app:preferenceTitle="Preference #2"/>
</PreferenceScreen>
It's generally wrong to provide a string resource as a styleable and that's why you're vastly overcomplicating a simple localization job. Any resource can be accessed with this pattern: [<package_name>.]R.<resource_type>.<resource_name>.
So this would be res/values/strings.xml:
<resources>
<string name="title_preference_01">Title 01</string>
<string name="summary_preference_01">Summary 01</string>
<string name="title_preference_02">Title 02</string>
<string name="summary_preference_02">Summary 02</string>
<resources>
Which then can be accessed throughput the application:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="Settings">
<CustomPreference
android:title="#string/title_preference_01"
android:summary="#string/summary_preference_01"
android:key="pref_one" />
<CustomPreference
android:title="#string/title_preference_02"
android:summary="#string/summary_preference_02"
android:key="pref_two" />
</PreferenceScreen>
Localize your app explains this all in high detail.
Besides, even if it were actual stylable values and not strings commonly used for localization, you'd still need to apply the PreferenceWidgetCustom style to SpecialCustomComponent in order to access it's values.
I solved my problem. As Martin pointed out a styleable is not really intended for strings anyways. As it turns out, using the built-in android:title attribute instead of a custom styleable made things really easy. All I have to do is call this.getTitle() to access its value from my CustomPreference class.
public class CustomPreference extends Preference {
public CustomPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.preference_widget_custom);
}
// other required constructor overloads omitted
#Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
setPreferenceTitle();
}
private void setPreferenceTitle() {
((SpecialCustomComponent) holder.itemView).setText(this.getTitle());
}
}

android nested custom control xml attributes

I'm building an android compound control and nesting it into another compound control. The nested control, ThirtySecondIncrement, is a simple incrementing control with a minus then text field then plus so you can raise or lower the increment. I've made this control more general for my app allowing for a simple counter or 30-second increments or 1-minute increments. Here is the xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="counter_simple">0</integer>
<integer name="counter_30sec">1</integer>
<integer name="counter_1min">2</integer>
<declare-styleable name="ThirtySecondIncrement">
<attr name="countertype" format="integer"/>
<attr name="maxcount" format="integer"/>
</declare-styleable>
<declare-styleable name="IntervalEdit">
<attr name="label" format="string"/>
<attr name="incrementlabel" format="string"/>
</declare-styleable>
</resources>
My outer control includes labels and the ThirtySecondIncrement control. I would like to make the outer control flexible enough that I could include the "countertype" style to the outer control.
Can I do this in xml or must I do it programmatically? And if I do it programmatically how can I guarantee that it is done before the control is first used. Here is the code to extract the xml attributes:
public class ThirtySecondIncrement extends LinearLayout {
final int COUNT_INTEGER = 0;
final int COUNT_30SEC = 1;
final int COUNT_1MIN = 2;
//other code
public ThirtySecondIncrement(Context context, AttributeSet attr) {
super(context, attr);
TypedArray array = context.obtainStyledAttributes(attr, R.styleable.ThirtySecondIncrement, 0, 0);
m_countertype = array.getInt(R.styleable.ThirtySecondIncrement_countertype, COUNT_30SEC);
m_max = array.getInt(R.styleable.ThirtySecondIncrement_maxcount, MAXCOUNT);
m_increment = (m_countertype == COUNT_1MIN) ? 2 : 1;
array.recycle();
Initialize(context);
}
In a similar function in my IntervalEdit I could get an attribute relating to the counter and use a public function in ThirtySecondIncrement to set the countertype but, as stated, I'm wondering if there's a way to do this in xml.
thanks, in advance
I waited a while but got no answer so I solved the problem by having a helper function in the nested control to enable to set the property at runtime.
public void SetCounterType(integer countertype) {
//after checking that countertype is a valid value
m_countertype = countertype;
}
then in the constructor for IntervalEdit:
public IntervalEdit(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.IntervalEdit, 0, 0);
//other attributes read here
m_counterstyle = array.getInt(R.styleable.IntervalEdit_counterstyle, R.integer.counter_simple);
(ThirtySecondIncrement)findViewById(R.id.tsi).SetCounterStyle(m_counterstyle);
The SetCounterStyle function in the first custom control only sets the variable and doesn't force a redraw enabling me to call it from within the including custom control's constructor.
Any, that's how I solved it.

android:foreground attribute / setForeground() method does not work with Button elements

Since Android 23, the android:foreground XML attribute (and corresponding setForeground() method) should be available to all Views, and not just FrameLayout instances as was previously the case.
Yet, for some reason, whenever I create a Button instance which inherits from TextView -> View, I can't seem to get a foreground to show at all.
Here is an example button definition that doesn't work appear to show the foreground:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:text="Primary Full Width"
style="#style/Button.Primary" />
And here is the defintion of the Button.Primary style:
<style name="Button.Primary" parent="Widget.AppCompat.Button">
<item name="android:background">#drawable/button_primary_background_selector</item>
<item name="android:foreground">#drawable/button_primary_foreground_selector</item>
<item name="android:textAppearance">#style/TextAppearance.FF.Button</item>
<item name="android:minHeight">80dp</item>
<item name="android:height">80dp</item>
<item name="android:minWidth">200dp</item>
<item name="android:defaultWidth">288dp</item>
<item name="android:maxWidth">400dp</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:gravity">center_vertical|center_horizontal</item>
<item name="android:paddingStart">70dp</item>
<item name="android:paddingEnd">70dp</item>
<item name="android:stateListAnimator">#null</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
<item name="android:drawablePadding">10dp</item>
</style>
I've confirmed that setting the foreground attribute in the style or in the <Button> definition does not change anything, the foreground fails to show regardless. The background shows correctly, as does the text, it's just the foreground that's missing.
I've also tried setting the foreground to a solid color instead of a state selector drawable, with no success.
After removing the style elements one by one and retesting, I narrowed down the issue to the inclusion of the android:singleLine attribute in the style.
Removing this attribute made it so any TextViews or Buttons using my style would properly show the foreground drawable as desired. Looking through the implementation of TextView.java where singleLine is defined, I'm still struggling to determine how setting this attribute causes foregrounds to be ignored, but I will update this answer if I find out.
I know that use of singleLine is deprecated but unfortunately I still need the functionality provided by the combination of the singleLine / ellipsize attributes which are not available when replacing with maxLines usage.
Edit: After writing out my answer above, I decided to do some more investigation and uncovered a few more details.
First, I created a custom view that extended from AppCompatButton so I could attempt to re-implement the functionality singleLine / ellipsize provided (namely, showing text on a single line by replacing newline characters with spaces and then also adding ellipsis if the text ran off the view).
Reading through the TextView source code, I found a section of code that is called when the singleLine attribute is set to true:
private void applySingleLine(boolean singleLine, boolean applyTransformation,
boolean changeMaxLines) {
mSingleLine = singleLine;
if (singleLine) {
setLines(1);
setHorizontallyScrolling(true);
if (applyTransformation) {
setTransformationMethod(SingleLineTransformationMethod.getInstance());
}
}
...
}
So I tried extracting those lines and adding to my own implementation:
private void updateMaxlinesLocally(int maxLines) {
if (maxLines == 1) {
Log.d("Button", "max lines was 1, setting to single line equivalent");
setLines(1);
setHorizontallyScrolling(true);
setTransformationMethod(SingleLineTransformationMethod.getInstance());
// reset any text that may have already been set
setText(getText());
} else {
Log.d("Button", "max lines was : " + maxLines);
}
}
But when this code ran, I saw the same issue from before where foreground drawables would no longer be visible.
After further testing, I was able to narrow down the issue to the setHorizontallyScrolling(true) method call. If I commented just this line out of my custom implementation, I'm able to preserve the singleLine / ellipsize functionality I had before and foreground drawables show as expected! Narrowing it down to that method still didn't help me figure out the root cause in the TextView base implementation, but if anyone has any details on that, feel free to comment with more information.
Here is my final custom Button class which simply looks at the maxLines attribute and simulates the old singleLine functionality when maxLines is set to 1, avoiding the method call that would prevent foregrounds from showing. In case it's useful...
public class Button extends AppCompatButton {
public Button(Context context) {
super(context);
init(context, null, 0);
}
public Button(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public Button(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
#Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
updateMaxlinesLocally(maxLines);
}
private void init(Context context, AttributeSet attributeSet, int defStyleAttr) {
int[] set = {
android.R.attr.maxLines
};
TypedArray a = context.obtainStyledAttributes(
attributeSet, R.styleable.Button);
int maxLines = a.getInt(R.styleable.Button_android_maxLines, -1);
a.recycle();
updateMaxlinesLocally(maxLines);
}
private void updateMaxlinesLocally(int maxLines) {
if (maxLines == 1) {
Log.d("Button", "max lines was 1, setting to single line equivalent");
setLines(1);
// The act of setting horizontally scrolling to true is what disables foregrounds from
// showing...
// setHorizontallyScrolling(true);
setTransformationMethod(SingleLineTransformationMethod.getInstance());
// reset any text that may have already been set
setText(getText());
} else {
Log.d("Button", "max lines was : " + maxLines);
}
}
}
and from attrs.xml:
<declare-styleable name="Button">
<attr name="android:maxLines" format="integer" />
</declare-styleable>
As stated by Keith setHorizontallyScrolling(true), scrolling makes the foreground disappear. One can easily reproduce the problem.
But why does horizontal scrolling affect the foreground?
Setting horizontal (or vertical scroll) tells the TextView it contains a larger area than it's layout bounds.
If you print the scrollX / scrollY values, normally it won't be zeroes but a very big number instead, even if the content is not scrollable by users.
On the other hand, the foreground is designed to scroll with the content, so it's basically drawn starting from (0, 0) of the canvas, not the top left corner of the visible content. So it's drawn far far away from the actual view bounds. You can open the View.java for the source code of onDrawForeground for that.
What can I do
Disable scroll by calling setHorizontallyScrolling(false) after setting the singleline attribute or anything that implies scrolling. Beware not to get scroll enabled trying to initialize / update the view.
translate the foreground drawable, e.g. update the bounds from setBounds by adding scrollX & scrollY

Need the image id from xml attributes custom widget

I have a custom control (very simple for now) that is like a button. It needs to display an unpressed and a pressed image. It appears multiple times in the activity and has different pairs of images depending on where it's used. Think of toolbar icons - similar to that.
Here's an extract of my layout:
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:MyApp="http://schemas.android.com/apk/res/com.example.mockup"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TableRow>
<com.example.mockup.ImageGestureButton
android:id="#+id/parent_arrow"
android:src="#drawable/parent_arrow"
MyApp:srcPressed="#drawable/parent_arrow_pressed"
... />
...
</TableRow>
</TableLayout>
attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ImageGestureButton">
<attr name="srcPressed" format="reference" />
</declare-styleable>
</resources>
And, in the R.java, one finds:
public static final class drawable {
public static final int parent_arrow=0x7f020003;
public static final int parent_arrow_pressed=0x7f020004;
...
}
During widget instantiation, I want to determine the ids declared in the activity xml. How do I do that? I've tried this (I updated my original post with working code; so, the following works.)
public class ImageGestureButton extends ImageView
implements View.OnTouchListener
{
private Drawable unpressedImage;
private Drawable pressedImage;
public ImageGestureButton (Context context, AttributeSet attrs)
{
super(context, attrs);
setOnTouchListener (this);
unpressedImage = getDrawable();
TypedArray a = context.obtainStyledAttributes (attrs, R.styleable.ImageGestureButton, 0, 0);
pressedImage = a.getDrawable (R.styleable.ImageGestureButton_srcPressed);
}
public boolean onTouch (View v, MotionEvent e)
{
if (e.getAction() == MotionEvent.ACTION_DOWN)
{
setImageDrawable (pressedImage);
}
else if (e.getAction() == MotionEvent.ACTION_UP)
{
setImageDrawable (unpressedImage);
}
return false;
}
}
If you want to get the drawable use TypedArray.getDrawable(). In your example you are using getString().
In your declare-styleable use
<attr name="srcPressed" format="reference" />
If you want the actual resource ID for the Drawable, rather than the fully resolved Drawable it's self, you can do this:
TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.FooLayout );
TypedValue value = new TypedValue();
a.getValue( R.styleable.FooLayout_some_attr, value );
Log.d( "DEBUG", "This is the actual resource ID: " + value.resourceId );

Mimic the Overflow Spinner in a ListView Row

I'm trying to put in a Spinner on each row of a ListView within a ListFragment.
I want it to look like a vertical overflow image like in the store but I'm not able to figure out how to show the vertical overflow image that is clickable to show the options.
It always looks like below instead. I would like to remove "Options" and have the overflow image instead.
Any help is appreciated.
Found relevant ideas from other posts and combined them, thank you Stack Overflow.
Android: How to set spinner selector to own image/icon?
Declaring a custom android UI element using XML
How to get width and height of the image?
The idea is that you create a 0dp width Spinner with an ImageView over it. When you click the image, it shows the drop down. I haven't tested it's behavior when the Spinner is at the edge of the screen yet and may very well cause trouble. I also need to tweak the position of the Spinner, but this works for now.
My plan is to catch the selection from the Spinner and then open a dialog / intent based on what was clicked. Here is what it looks like. (the ImageView is faint but it's mostly a placehodler for me right now)
Before click
After click
Here is the general code I used since this seems desirable to others.
values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="OverflowSpinner">
<attr name="imageResource" format="string" />
<attr name="spinnerTextResource" format="string" />
</declare-styleable>
</resources>
values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="spinner_array">
<item>Skip</item>
<item>View log</item>
</string-array>
</resources>
layouts/row.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:awesome="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- stuff -->
<com.blah.package.OverflowSpinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
awesome:imageResource="#drawable/ic_menu_moreoverflow_normal_holo_light"
awesome:spinnerTextResource="#array/spinner_array"
/>
</RelativeLayout>
OverflowSpinner.java
public class OverflowSpinner extends RelativeLayout {
int mImage;
int mStrings;
public OverflowSpinner(Context context) {
super(context);
}
public OverflowSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
setupDisplay(context);
}
public OverflowSpinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
setupDisplay(context);
}
private void init(AttributeSet attrs) {
TypedArray attribs = getContext().obtainStyledAttributes(attrs, R.styleable.OverflowSpinner);
// get attributes
mImage = attribs.getResourceId(R.styleable.OverflowSpinner_imageResource, -1);
mStrings = attribs.getResourceId(R.styleable.OverflowSpinner_spinnerTextResource, -1);
attribs.recycle();
}
private void setupDisplay(Context context) {
BitmapDrawable bitmap = (BitmapDrawable)this.getResources().getDrawable(mImage);
int height = bitmap.getBitmap().getHeight();
// set size of Spinner to 0 x height so it's "hidden"
// the height is used to help position the Spinner in a nicer spot
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, mStrings, android.R.layout.simple_spinner_item);
// setup spinner
final Spinner spinner = new Spinner(context);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setLayoutParams(lp);
this.addView(spinner);
// set size of image to be normal
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_BOTTOM);
ImageButton option = new ImageButton(context);
option.setBackgroundResource(android.R.color.transparent);
option.setImageResource(mImage);
option.setLayoutParams(lp);
// when clicking the image button, trigger the spinner to show
option.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
spinner.performClick();
}
});
this.addView(option);
}
}

Categories

Resources