Define custom properties

Keywords: Android xml Attribute encoding

I need to implement my own properties, such as com.android.R.attr

Nothing was found in the official documentation, so I need information on how to define these properties and how to use them from my code.

#1st floor

Qberticus has a good answer, but lacks a useful detail.If you want to implement these in the library, replace:

xmlns:whatever="http://schemas.android.com/apk/res/org.example.mypackage"

And:

xmlns:whatever="http://schemas.android.com/apk/res-auto"

Otherwise, runtime errors will occur for applications that use the library.

#2nd floor

The answer above covers all the details except two.

First, if there are no styles, the preferences will be instantiated using the (Context context, AttributeSet attrs) method signature.In this case, simply use context.obtainStyledAttributes(attrs, R.styleable.MyCustomView) to get the TypedArray.

Second, it does not cover how resources (quantity strings) are handled.These cannot be handled with TypedArray.This is a snippet of my SeekBarPreference code that sets a summary of preferences and formats their values based on their values.If the xml of the preference sets android:summary as a text string or string source, the value of the preference is formatted as a string (which should contain%d to get the value).If android:summary is set to a plaurals resource, it is used to format the results.

// 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);
}
  • As an example, however, if you want to set a summary on the preferences screen, you need to call notifyChanged() in the preferences onDialogClosed method.

#3rd floor

Currently, the best document is the source.You can do this at Here (attrs.xml) View.

You can define attributes in the top <resources>element or within the <declare-styleable>element.If you want to use attr in more than one place, place it in the root element.Note that all attributes share the same global namespace.This means that even if you create a new attribute inside the <declare-styleable>element, you can use it outside, and you cannot create another attribute with the same name and different types.

The <attr>element has two xml attribute names and formats.name allows you to call it, which is how you end up referencing it in your code, such as R.attr.my_attribute.The format attribute can have different values, depending on the "type" of the desired attribute.

  • Reference-If it refers to another resource ID (e.g.'@ color / my_color','@ layout / my_layout')
  • colour
  • Boolean Value
  • size
  • Float
  • integer
  • strand
  • Fraction
  • Enumeration - usually implicitly defined
  • Flag - usually implicitly defined

You can use | to set the format to a variety of types, such as format="reference|color".

The enum attribute 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 that you need to define values so you can place them together:

<attr name="my_flag_attr">
  <flag name="fuzzy" value="0x01" />
  <flag name="cold" value="0x02" />
</attr>

In addition to attributes, there are <declare-styleable>elements.This allows you to define properties that can be used by custom views.You can do this by specifying the <attr> element, but if it was previously defined, format is not specified.If you want to reuse an android attr, such as android:gravity, you can use name, as shown below.

Custom View <declare-styleable>:

<declare-styleable name="MyCustomView">
  <attr name="my_custom_attribute" />
  <attr name="android:gravity" />
</declare-styleable>

There are a few things you need to do when defining custom attributes in XML on a custom view.First, you must declare a namespace to find your attributes.You can do this on the root layout element.Usually only xmlns:android="http://schemas.android.com/apk/res/android".Now you must 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 this custom property, you can usually do the following in the constructor of a custom view.

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

End.:)

#4th floor

Traditional methods are full of boilerplate code and clumsy resource handling.That's why I made it Spyglass Framework .To demonstrate how this works, the following example shows how to make a custom view that displays the title of a String.

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 property 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 property values to it when the view is zoomed in.

@HandlesString(attributeId = R.styleable.CustomView_title)
public void setTitle(String title) {
    titleView.setText(title);
}

Now that your class has the Spyglass annotation, the Spyglass framework detects it at compile time and automatically generates the CustomView_SpyglassCompanion class.

Step 4: Use the generated classes in the init method of the custom view:

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

Nothing more.Now, when you instantiate a class from XML, the Spyglass companion interprets the properties and makes the required method calls.For example, if we add the following layout, setTitle will be called "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 is not limited to string resources, but has many different comments for working with other resource types.It also has notes for defining default values and passing placeholder values when the method has multiple parameters.

For more information and examples, check out the Github repository.

#5th floor

If you omit the format attribute of the attr element, you can use it to reference classes in an XML layout.

  • Come from attrs.xml Example.
  • Android Studio knows that this class is referenced from XML
    • That is
      • Refactor > Rename Works
      • Find Usages is valid
      • Wait...

Do not specify the 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 files... / SRC / main / RES / layout / activity_u 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>

Resolve classes 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()
            }
        }
    }

Posted by Stevan on Sat, 25 Jan 2020 20:50:19 -0800