Definición de atributos personalizados
Necesito implementar mis propios atributos como encom.android.R.attr
No encontré nada en la documentación oficial, por lo que necesito información sobre cómo definir estos atributos y cómo usarlos desde mi código.
Actualmente la mejor documentación es la fuente. Puedes echarle un vistazo aquí (attrs.xml) .
Puede definir atributos en el <resources>
elemento superior o dentro de un <declare-styleable>
elemento. Si voy a usar un atributo en más de un lugar, lo coloco en el elemento raíz. Tenga en cuenta que todos los atributos comparten el mismo espacio de nombres global. Eso significa que incluso si crea un nuevo atributo dentro de un <declare-styleable>
elemento, puede usarse fuera de él y no puede crear otro atributo con el mismo nombre de un tipo diferente.
Un <attr>
elemento tiene dos atributos xml name
y format
. name
te permite llamarlo de alguna manera y así es como terminas refiriéndote a él en el código, por ejemplo, R.attr.my_attribute
. El format
atributo puede tener diferentes valores dependiendo del 'tipo' de atributo que desee.
- referencia: si hace referencia a otra identificación de recurso (por ejemplo, "@color/my_color", "@layout/my_layout")
- color
- booleano
- dimensión
- flotar
- entero
- cadena
- fracción
- enumeración - normalmente definida implícitamente
- bandera - normalmente definida implícitamente
Puede configurar el formato en varios tipos utilizando |
, por ejemplo, format="reference|color"
.
enum
Los atributos se pueden definir de la siguiente manera:
<attr name="my_enum_attr">
<enum name="value1" value="1" />
<enum name="value2" value="2" />
</attr>
flag
Los atributos son similares excepto que los valores deben definirse para que puedan unirse entre sí:
<attr name="my_flag_attr">
<flag name="fuzzy" value="0x01" />
<flag name="cold" value="0x02" />
</attr>
Además de los atributos está el <declare-styleable>
elemento. Esto le permite definir atributos que puede usar una vista personalizada. Esto lo haces especificando un <attr>
elemento, si se definió previamente no especificas el format
. Si desea reutilizar un atributo de Android, por ejemplo, android:gravity, puede hacerlo en el archivo name
, de la siguiente manera.
Un ejemplo de una vista personalizada <declare-styleable>
:
<declare-styleable name="MyCustomView">
<attr name="my_custom_attribute" />
<attr name="android:gravity" />
</declare-styleable>
Al definir sus atributos personalizados en XML en su vista personalizada, debe hacer algunas cosas. Primero debes declarar un espacio de nombres para encontrar tus atributos. Esto se hace en el elemento de diseño raíz. Normalmente solo hay xmlns:android="http://schemas.android.com/apk/res/android"
. Ahora también debes agregar xmlns:whatever="http://schemas.android.com/apk/res-auto"
.
Ejemplo:
<?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>
Finalmente, para acceder a ese atributo personalizado, normalmente lo hace en el constructor de su vista personalizada de la siguiente manera.
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();
}
El fin. :)
La respuesta de Qberticus es buena, pero falta un detalle útil. Si está implementando estos en una biblioteca, reemplace:
xmlns:whatever="http://schemas.android.com/apk/res/org.example.mypackage"
con:
xmlns:whatever="http://schemas.android.com/apk/res-auto"
De lo contrario, la aplicación que utiliza la biblioteca tendrá errores de ejecución.
La respuesta anterior cubre todo con gran detalle, excepto un par de cosas.
En primer lugar, si no hay estilos, (Context context, AttributeSet attrs)
se utilizará la firma del método para crear una instancia de la preferencia. En este caso, simplemente utilícelo context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)
para obtener TypedArray.
En segundo lugar, no cubre cómo tratar con recursos plurales (cadenas de cantidad). Estos no se pueden solucionar utilizando TypedArray. Aquí hay un fragmento de código de mi SeekBarPreference que establece el resumen de la preferencia formateando su valor de acuerdo con el valor de la preferencia. Si el xml de la preferencia establece android:summary en una cadena de texto o un recurso de cadena, el valor de la preferencia se formatea en la cadena (debe tener %d, para recoger el valor). Si android:summary está configurado como un recurso plaurals, se usa para formatear el resultado.
// 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);
}
- Esto se proporciona solo como ejemplo; sin embargo, si desea configurar el resumen en la pantalla de preferencias, debe llamar
notifyChanged()
alonDialogClosed
método de preferencias.
El enfoque tradicional está lleno de código repetitivo y un manejo torpe de recursos. Por eso creé el marco Spyglass . Para demostrar cómo funciona, aquí hay un ejemplo que muestra cómo crear una vista personalizada que muestre un título de cadena.
Paso 1: cree una clase de vista personalizada.
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);
}
}
Paso 2: Defina un atributo de cadena en el values/attrs.xml
archivo de recursos:
<resources>
<declare-styleable name="CustomView">
<attr name="title" format="string"/>
</declare-styleable>
</resources>
Paso 3: aplique la @StringHandler
anotación al setTitle
método para indicarle al marco de Spyglass que dirija el valor del atributo a este método cuando la vista esté inflada.
@HandlesString(attributeId = R.styleable.CustomView_title)
public void setTitle(String title) {
titleView.setText(title);
}
Ahora que su clase tiene una anotación Spyglass, el marco Spyglass la detectará en tiempo de compilación y generará automáticamente la CustomView_SpyglassCompanion
clase.
Paso 4: use la clase generada en el init
método de la vista personalizada:
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();
}
Eso es todo. Ahora, cuando crea una instancia de la clase desde XML, el complemento Spyglass interpreta los atributos y realiza la llamada al método requerido. Por ejemplo, si inflamos el siguiente diseño, setTitle
se llamará con "Hello, World!"
como argumento.
<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>
El marco no se limita a recursos de cadenas y tiene muchas anotaciones diferentes para manejar otros tipos de recursos. También tiene anotaciones para definir valores predeterminados y para pasar valores de marcador de posición si sus métodos tienen múltiples parámetros.
Eche un vistazo al repositorio de Github para obtener más información y ejemplos.
Si omite el format
atributo del attr
elemento, puede usarlo para hacer referencia a una clase desde diseños XML.
- ejemplo de attrs.xml .
- Android Studio entiende que se hace referencia a la clase desde XML
- es decir
Refactor > Rename
obrasFind Usages
obras- etcétera...
- es decir
no especifique un format
atributo en .../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>
Úselo en algún archivo de diseño .../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>
analiza la clase en el código de inicialización de tu vista .../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()
}
}
}