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 );
Related
I'm working on a project that relies on the user adding a custom attribute to their Android layout elements. Just like MvvmCross' app:MvxBind. There are no custom view classes as the idea is that the user can use the normal Android views.
The problem is that in order to get the value of this tag I need to get the IAttributeSet that is used during the view inflation process and I can't find a method of doing so that suits my needs.
I have a working example using LayoutInflater.IFactory however, this requires me to set my own LayoutInflater/factory which, if the user is using a library such as MvvmCross, causes problems as only one factory can be set at once.
I'm looking for a way that I can get the IAttributeSet object whenever a view is inflated to check for my attribute that doesn't interfere with the standard LayoutInflater or LayoutInflater's from other libraries. Or if there is any way to get my attribute after the view has been inflated.
Thanks in advance!
Edit
I want to be able to get the value of MyAttribute from a view without subclassing views or creating custom views. This is easily accomplished with LayoutInflater.IFactory but this method interferes with libraries such as MvvmCross.
<TextView
android:layout_width="wrap_content"
android:layout_width="wrap_content"
app:MyAttribute="My attribute value" />
I'm not sure if I understand what you mean. Please refer to the following:
you could create a custom view and define its attribute in attrs.xml and when it is created from an XML layout, all of the attributes in the XML tag are read from the resource bundle and passed into the view’s constructor as an IAttributeSet
for example,here is a custom view named IconView
public class IconView : View
{
}
define some attributes in attrs.xml(Resources/values/attrs.xml)
<declare-styleable name="IconView">
<attr name="bg_color" format="color" />
<attr name="src" format="integer" />
<attr name="showIconLabel" format="boolean" />
<attr name="iconLabelTextColor" format="color" />
<attr name="iconLabelText" format="string" />
</declare-styleable>
then we could process the attributes in the view constructor when it is created (here define an Initialize method):
public IconView(Context context) : base(context)
{
Initialize(context);
}
public IconView(Context context, IAttributeSet attrs) : base(context, attrs)
{
Initialize(context, attrs);
}
private void Initialize(Context context, IAttributeSet attrs = null)
{
if (attrs != null)
{
// Contains the values set for the styleable attributes you declared in your attrs.xml
var array = context.ObtainStyledAttributes(attrs, Resource.Styleable.IconView, 0, 0);
iconBackgroundColor = array.GetColor(Resource.Styleable.IconView_bg_color, Color.Gray);
iconLabelTextColor = array.GetColor(Resource.Styleable.IconView_iconLabelTextColor, Color.ParseColor("#D9000000"));
_labelText = array.GetString(Resource.Styleable.IconView_iconLabelText);
_showIconLabel = array.GetBoolean(Resource.Styleable.IconView_showIconLabel, false);
var iconResId = array.GetResourceId(Resource.Styleable.IconView_src, 0);
if (iconResId != 0) // If the user actually set a drawable
_icon = AppCompatDrawableManager.Get().GetDrawable(context, iconResId);
// If the users sets text for the icon without setting the showIconLabel attr to true
// set it to true for the user anyways
if (_labelText != null)
_showIconLabel = true;
// Very important to recycle the array after use
array.Recycle();
}
...
}
You can refer to it for more details make custom view
I finally managed to figure this out.
Non-MvvmCross Solution
I stumbled across an article called "Layout Inflater: Friend or Foe?". I think this is the link but at the time of posting this answer it isn't working.
The author did an amazing talk on LayoutInflater and how he changed the Android LayoutInflater process so that he could intercept it for his library Calligraphy. The resulting solution is called ViewPump and it's written in Kotlin.
I have written the ViewPump library in Xamarin for use with non-MvvmCross projects: https://github.com/lewisbennett/viewpump.
MvvmCross Solution
MvvmCross uses a solution based on InflationX' ViewPump to do its binding and we can access it by first creating the below classes:
public class BindingBuilder : MvxAndroidBindingBuilder
{
protected override IMvxAndroidViewBinderFactory CreateAndroidViewBinderFactory()
{
return new ViewBinderFactory();
}
}
public class ViewBinderFactory : IMvxAndroidViewBinderFactory
{
public IMvxAndroidViewBinder Create(object source)
{
return new ViewBinder(source);
}
}
public class ViewBinder : MvxAndroidViewBinder
{
public override void BindView(View view, Context context, IAttributeSet attrs)
{
base.BindView(view, context, attrs);
// Do your intercepting here.
}
public ViewBinder(object source)
: base(source)
{
}
}
Then in your MvxAndroidSetup or MvxAppCompatSetup class add the following:
protected override MvxBindingBuilder CreateBindingBuilder()
{
return new BindingBuilder();
}
Done! I hope this helps someone :)
It's my very first question here, so please go easy on me ;)
I've built my custom View class extending ImageView.
public class CustomImageView extends ImageView {
// ...
}
I have created a set of custom parameters for it in the shape of a <declare-styleable> item in the attrs.xml file.
<declare-styleable name="CustomImageView">
<attr name="angle" format="integer"/>
</declare-styleable>
I've figured out how to access (i.e. read from within the class and set from within the layout) these values.
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomImageView, 0, 0);
try {
a.getInt(R.styleable.CustomImageView_angle, 0);
} finally {
a.recycle();
}
So far, so easy. All of the above are directly taken from the guide.
However, I could not figure out how to access the inherited attributes of the ImageView class. Specifically, I want to read what was set as the src attribute of the ImageView. I'm assuming I have to use a different value for the second parameter of the obtainStyledAttributes(...) call, but I don't know what to use there and this obviously does not work:
a = context.getTheme().obtainStyledAttributes(attrs, ImageView, 0, 0);
So, how do I access the built-in attributes of my super class?
How do I get the int value (drawable res id) that was set for the android:src attribute?
Thanks for your help!
How do I get the int value (drawable res id) that was set for the
android:src attribute?
Use getAttributeResourceValue to get id of drawable :
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
String android_schemas = "http://schemas.android.com/apk/res/android";
int srcId = attrs.getAttributeResourceValue(android_schemas, "src", -1);
}
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.
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);
}
}
I need to implement my own attributes like in com.android.R.attr
Found nothing in official documentation so I need information about how to define these attrs and how to use them from my code.
Currently the best documentation is the source. You can take a look at it here (attrs.xml).
You can define attributes in the top <resources> element or inside of a <declare-styleable> element. If I'm going to use an attr in more than one place I put it in the root element. Note, all attributes share the same global namespace. That means that even if you create a new attribute inside of a <declare-styleable> element it can be used outside of it and you cannot create another attribute with the same name of a different type.
An <attr> element has two xml attributes name and format. name lets you call it something and this is how you end up referring to it in code, e.g., R.attr.my_attribute. The format attribute can have different values depending on the 'type' of attribute you want.
reference - if it references another resource id (e.g, "#color/my_color", "#layout/my_layout")
color
boolean
dimension
float
integer
string
fraction
enum - normally implicitly defined
flag - normally implicitly defined
You can set the format to multiple types by using |, e.g., format="reference|color".
enum attributes can be defined as follows:
<attr name="my_enum_attr">
<enum name="value1" value="1" />
<enum name="value2" value="2" />
</attr>
flag attributes are similar except the values need to be defined so they can be bit ored together:
<attr name="my_flag_attr">
<flag name="fuzzy" value="0x01" />
<flag name="cold" value="0x02" />
</attr>
In addition to attributes there is the <declare-styleable> element. This allows you to define attributes a custom view can use. You do this by specifying an <attr> element, if it was previously defined you do not specify the format. If you wish to reuse an android attr, for example, android:gravity, then you can do that in the name, as follows.
An example of a custom view <declare-styleable>:
<declare-styleable name="MyCustomView">
<attr name="my_custom_attribute" />
<attr name="android:gravity" />
</declare-styleable>
When defining your custom attributes in XML on your custom view you need to do a few things. First you must declare a namespace to find your attributes. You do this on the root layout element. Normally there is only xmlns:android="http://schemas.android.com/apk/res/android". You must now also add xmlns:whatever="http://schemas.android.com/apk/res-auto".
Example:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:whatever="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<org.example.mypackage.MyCustomView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
whatever:my_custom_attribute="Hello, world!" />
</LinearLayout>
Finally, to access that custom attribute you normally do so in the constructor of your custom view as follows.
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0);
String str = a.getString(R.styleable.MyCustomView_my_custom_attribute);
//do something with str
a.recycle();
}
The end. :)
Qberticus's answer is good, but one useful detail is missing. If you are implementing these in a library replace:
xmlns:whatever="http://schemas.android.com/apk/res/org.example.mypackage"
with:
xmlns:whatever="http://schemas.android.com/apk/res-auto"
Otherwise the application that uses the library will have runtime errors.
The answer above covers everything in great detail, apart from a couple of things.
First, if there are no styles, then the (Context context, AttributeSet attrs) method signature will be used to instantiate the preference. In this case just use context.obtainStyledAttributes(attrs, R.styleable.MyCustomView) to get the TypedArray.
Secondly it does not cover how to deal with plaurals resources (quantity strings). These cannot be dealt with using TypedArray. Here is a code snippet from my SeekBarPreference that sets the summary of the preference formatting its value according to the value of the preference. If the xml for the preference sets android:summary to a text string or a string resouce the value of the preference is formatted into the string (it should have %d in it, to pick up the value). If android:summary is set to a plaurals resource, then that is used to format the result.
// Use your own name space if not using an android resource.
final static private String ANDROID_NS =
"http://schemas.android.com/apk/res/android";
private int pluralResource;
private Resources resources;
private String summary;
public SeekBarPreference(Context context, AttributeSet attrs) {
// ...
TypedArray attributes = context.obtainStyledAttributes(
attrs, R.styleable.SeekBarPreference);
pluralResource = attrs.getAttributeResourceValue(ANDROID_NS, "summary", 0);
if (pluralResource != 0) {
if (! resources.getResourceTypeName(pluralResource).equals("plurals")) {
pluralResource = 0;
}
}
if (pluralResource == 0) {
summary = attributes.getString(
R.styleable.SeekBarPreference_android_summary);
}
attributes.recycle();
}
#Override
public CharSequence getSummary() {
int value = getPersistedInt(defaultValue);
if (pluralResource != 0) {
return resources.getQuantityString(pluralResource, value, value);
}
return (summary == null) ? null : String.format(summary, value);
}
This is just given as an example, however, if you want are tempted to set the summary on the preference screen, then you need to call notifyChanged() in the preference's onDialogClosed method.
The traditional approach is full of boilerplate code and clumsy resource handling. That's why I made the Spyglass framework. To demonstrate how it works, here's an example showing how to make a custom view that displays a String title.
Step 1: Create a custom view class.
public class CustomView extends FrameLayout {
private TextView titleView;
public CustomView(Context context) {
super(context);
init(null, 0, 0);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0, 0);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs, defStyleAttr, 0);
}
#RequiresApi(21)
public CustomView(
Context context,
AttributeSet attrs,
int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs, defStyleAttr, defStyleRes);
}
public void setTitle(String title) {
titleView.setText(title);
}
private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
inflate(getContext(), R.layout.custom_view, this);
titleView = findViewById(R.id.title_view);
}
}
Step 2: Define a string attribute in the values/attrs.xml resource file:
<resources>
<declare-styleable name="CustomView">
<attr name="title" format="string"/>
</declare-styleable>
</resources>
Step 3: Apply the #StringHandler annotation to the setTitle method to tell the Spyglass framework to route the attribute value to this method when the view is inflated.
#HandlesString(attributeId = R.styleable.CustomView_title)
public void setTitle(String title) {
titleView.setText(title);
}
Now that your class has a Spyglass annotation, the Spyglass framework will detect it at compile-time and automatically generate the CustomView_SpyglassCompanion class.
Step 4: Use the generated class in the custom view's init method:
private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
inflate(getContext(), R.layout.custom_view, this);
titleView = findViewById(R.id.title_view);
CustomView_SpyglassCompanion
.builder()
.withTarget(this)
.withContext(getContext())
.withAttributeSet(attrs)
.withDefaultStyleAttribute(defStyleAttr)
.withDefaultStyleResource(defStyleRes)
.build()
.callTargetMethodsNow();
}
That's it. Now when you instantiate the class from XML, the Spyglass companion interprets the attributes and makes the required method call. For example, if we inflate the following layout then setTitle will be called with "Hello, World!" as the argument.
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:width="match_parent"
android:height="match_parent">
<com.example.CustomView
android:width="match_parent"
android:height="match_parent"
app:title="Hello, World!"/>
</FrameLayout>
The framework isn't limited to string resources has lots of different annotations for handling other resource types. It also has annotations for defining default values and for passing in placeholder values if your methods have multiple parameters.
Have a look at the Github repo for more information and examples.
if you omit the format attribute from the attr element, you can use it to reference a class from XML layouts.
example from attrs.xml.
Android Studio understands that the class is being referenced from XML
i.e.
Refactor > Rename works
Find Usages works
and so on...
don't specify a format attribute in .../src/main/res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyCustomView">
....
<attr name="give_me_a_class"/>
....
</declare-styleable>
</resources>
use it in some layout file .../src/main/res/layout/activity__main_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<SomeLayout
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- make sure to use $ dollar signs for nested classes -->
<MyCustomView
app:give_me_a_class="class.type.name.Outer$Nested/>
<MyCustomView
app:give_me_a_class="class.type.name.AnotherClass/>
</SomeLayout>
parse the class in your view initialization code .../src/main/java/.../MyCustomView.kt
class MyCustomView(
context:Context,
attrs:AttributeSet)
:View(context,attrs)
{
// parse XML attributes
....
private val giveMeAClass:SomeCustomInterface
init
{
context.theme.obtainStyledAttributes(attrs,R.styleable.ColorPreference,0,0).apply()
{
try
{
// very important to use the class loader from the passed-in context
giveMeAClass = context::class.java.classLoader!!
.loadClass(getString(R.styleable.MyCustomView_give_me_a_class))
.newInstance() // instantiate using 0-args constructor
.let {it as SomeCustomInterface}
}
finally
{
recycle()
}
}
}
HERE is the official documentation for creating custom attributes and Views