How to pass custom attributes to nested xml - android

I have structure like that:
preferences.xml:
...
<com.example.MyCustomPreference
...
myCustomMessage="#string/abc"
android:inputType="..."
... />
...
preference_my_custom.xml:
<LinearLayout ...>
<com.example.MyCustomView
...
app:myCustomMessage="?????"
... />
</LinearLayout>
view_my_custom.xml:
<GridView ...>
...EditTexts, TextViews, etc.
</GridView>
I would like to pass myCustomMessage's value (I omitted other attributes for simplification) from MyCustomPreference to MyCustomView using XML. MyCustomView reads custom attributes, so I would like to avoid reading attributes in MyCustomPreference programmatically, getting TextViews from MyCustomView and setting them values. However, I really don't know what to type in place of "?????".
How can i do this using XML? Is this possible?

You have to do it programmatically (unless you use data binding). For example, in your MyCustomPreference you catch de attribute myCustomMessage:
String myCustomMessage = null;
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyCustomPreference, 0, 0);
try {
myCustomMessage = a.getString(R.styleable.MyCustomPreference_myCustomMessage);
} finally {
a.recycle();
}
Here you got the String value of your attribute. Then, I supose you have inflated your MyCustomView inside your MyCustomPreference. As an example:
View.inflate(getContext(), R.layout.preference_my_custom, this);
MyCustomView myCustomView = (MyCustomView) findViewById(R.id.you_custom_view_id);
So, here you can set programmatically your myCustomMessage in your MyCustomView.
myCustomView.setMyCustomMessage(myCustomMessage);
You should create this method to set correctly your text, and if necessary propagate this text to other child views of your MyCustomView.
Now, changing your String resId in your preferences.xml the interface should update as expected.
P.S: Since I don't know all your resource ids, please adapt them to your project.

Create an attribute file for your customeView:
Add in attrs.xml
<declare-styleable name="CustomView">
<attr name="width" format="dimension" />
</declare-styleable>
Used in your customView init:
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView, defStyle, 0);
mWidth = a.getDimensionPixelSize(R.styleable.CustomView_width,0);
a.recycle();
}

Related

Re-using android's built in xml attributes for a custom view [duplicate]

I wrote a custom view that extends RelativeLayout. My view has text, so I want to use the standard android:text without the need to specify a <declare-styleable> and without using a custom namespace xmlns:xxx every time I use my custom view.
this is the xml where I use my custom view:
<my.app.StatusBar
android:id="#+id/statusBar"
android:text="this is the title"/>
How can I get the attribute value? I think I can get the android:text attribute with
TypedArray a = context.obtainStyledAttributes(attrs, ???);
but what is ??? in this case (without a styleable in attr.xml)?
use this:
public YourView(Context context, AttributeSet attrs) {
super(context, attrs);
int[] set = {
android.R.attr.background, // idx 0
android.R.attr.text // idx 1
};
TypedArray a = context.obtainStyledAttributes(attrs, set);
Drawable d = a.getDrawable(0);
CharSequence t = a.getText(1);
Log.d(TAG, "attrs " + d + " " + t);
a.recycle();
}
i hope you got an idea
EDIT
Another way to do it (with specifying a declare-styleable but not having to declare a custom namespace) is as follows:
attrs.xml:
<declare-styleable name="MyCustomView">
<attr name="android:text" />
</declare-styleable>
MyCustomView.java:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);
CharSequence t = a.getText(R.styleable.MyCustomView_android_text);
a.recycle();
This seems to be the generic Android way of extracting standard attributes from custom views.
Within the Android API, they use an internal R.styleable class to extract the standard attributes and don't seem to offer other alternatives of using R.styleable to extract standard attributes.
Original Post
To ensure that you get all the attributes from the standard component, you should use the following:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextView);
CharSequence t = a.getText(R.styleable.TextView_text);
int color = a.getColor(R.styleable.TextView_textColor, context.getResources().getColor(android.R.color.darker_gray)); // or other default color
a.recycle();
If you want attributes from another standard component just create another TypedArray.
See http://developer.android.com/reference/android/R.styleable.html for details of available TypedArrays for standard components.

Read base class xml attribute from custom widget constructor?

I have a class derived from a standard library widget, how can I read one of the base class' xml attributes in the constructor? For example, how would I get the value of "android:layout_height" in the following?:
class MyTextView extends TextView {
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
int layoutHeightParamFromXmlAttributes = ?;
}
}
I'm interested in reading other "android:x" attributes in this way, this is just an example.
Thanks
You can try something like this:
In your widget constructor.
If you get custom attribute then try this.
if (attrs != null) {
TypedArray attributeArray = context.obtainStyledAttributes(attrs,R.styleable.CustomTextView);
//for font
String fontName = attributeArray.getString(R.styleable.CustomTextView_font_name);
}
And if you get default property like height and width then try this:
int width=attributeArray.getLayoutDimension(0,ViewGroup.LayoutParams.WRAP_CONTENT);
int height = attributeArray.getLayoutDimension(1,ViewGroup.LayoutParams.WRAP_CONTENT);//index is based on your xml file
Define style in attrs.xml file
<declare-styleable name="CustomTextView">
<attr name="font_name" format="string" />
</declare-styleable>
In your layout file add this:
<com.package.CustomTextView
android:id="#+id/customFTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:font_name="OpenSans-Regular.ttf"
android:gravity="center"/>
Hope this explanation helps you. :)

Alter AttributeSet values in android

I am having custom view which will take attribute set(xml value) as constructor value
public CustomView(Context context) // No Attributes in this one.
{
super(context);
this(context, null, 0);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
this(context, attrs, 0)
}
public CustomView(Context context, AttributeSet attrs, int default_style) {
super(context, attrs, default_style);
readAttrs(context, attrs, defStyle);
init();
}
In Fragment class i am setting the view as
CustomView customView = (CustomView) view.findViewById(R.id.customView);
where custom view contains various value such as height,width,padding etc.
i want to modify those values based on required condition and set it back to custom view.
I placed setting width height code in onDraw method and called invalidte view.
But above method will set the every time if i called invalidate method in CustomView class.
how to overcome this so that i can pass modified attribute set value in constructor only.?
Edit: I need to modify the view values(initialize with new values) which is set during attribute constructor so that i will get a refreshed view with a new values.
Override #OnDraw or 'Invalidate' is not a good function for me where inside invalidate i have written the methods which will execute in each second interval.
I see that your CustomView can have multiple attributes and you want to modify some of these attributes based on some condition and pass this in the constructor.
Few best practices while designing a custom view:
If you have custom attributes, make sure that you expose them via setters and getters. In your setter method, call invalidate();
Don't try modifying any attributes inside onDraw() or onMeasure() methods.
Try your best to avoid writing Custom constructors for your Custom View.
So the ideal way to solve your problem is to instantiate your CustomView and then modify the attributes, either externally (in your Activity or Fragment), or have a method inside the CustomView.java and then invoke it externally. Doing this will still give you the same result you are looking for.
So lets say you declared your custom attributes like this for view named StarsView
<declare-styleable name="StarsView">
<attr name="stars" format="integer" />
<attr name="score" format="float" />
</declare-styleable>
And you want to read attributes from something like this
<my.package..StarsView
app:stars="5"
app:score="4.6"
You do just this in constructor
public StarsView(Context context, AttributeSet attrs) {
super(context, attrs);
if(attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StarsView, defStyleAttr, 0);
stars = Tools.MathEx.clamp(1, 10, a.getInt(R.styleable.StarsView_stars, 5));
score = (int)Math.floor(a.getFloat(R.styleable.StarsView_score, stars) * 2f);
a.recycle(); // its important to call recycle after we are done
}
}
It's probably not the solution you were hoping for, but put a FrameLayout in your xml instead of the CustomView, and then create your CustomView programmatically with the FrameLayout as it's parent

android defined attributes and custom attributes side by side on a custom view

I have created a custom view as well as corresponding custom attributes. For example
<declare-styleable name="stripbar">
<attr name="flowDirection" format="string"/>
</declare-styleable>
and
public Stripbar(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.stripbar);
CharSequence flowDirection = ta.getString(R.styleable.stripbar_flowDirection);
String text = attrs.getAttributeValue("android", "text");
}
and
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ns="http://schemas.android.com/apk/res/com.ns">
<com.ns.Stripbar
android:id="#+id/aaa"
ns:flowDirection="RightToLeft"
android:text=”yoo hoo”/>
I receive null value for attribute text. I’m also aware of this and do not know how to resolve the issue.
To clarify, I need to obtain my custom attribute value as well as android predefined attribute value side by side?
Any help?
The problem is that your R.styleable.stripbar is actually an array of integers that contains just your flowDirection attribute.
To fix that, you should be able to replace your context.obtainStyledAttributes(attrs, R.styleable.stripbar); with context.obtainStyledAttributes(attrs, new int[]{R.attr.flowDirection, android.R.attr.text});
Then, you should be able to replace ta.getString( R.styleable.stripbar_flowDirection ) with ta.getString(0) and get the text with ta.getString(1).

Defining custom attrs

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

Categories

Resources