Definindo atributos personalizados

472

Eu preciso implementar meus próprios atributos como em com.android.R.attr

Não encontrei nada na documentação oficial, por isso preciso de informações sobre como definir esses atributos e como usá-los no meu código.

Alexander Oleynikov
fonte
20
Esses documentos podem ser mais recentes que sua postagem, mas para manter essa atualização, você pode encontrar boa documentação oficial para os atributos aqui: developer.android.com/training/custom-views/…
OYRM
Eu recomendo um bom artigo com um exemplo sobre os atributos personalizados: amcmobileware.org/android/blog/2016/09/11/custom-attributes
Arkadiusz Cieśliński
um pequeno exemplo de trabalho pode ser útil: github.com/yujiaao/MergeLayout1
Yu Jiaao 2/16

Respostas:

971

Atualmente, a melhor documentação é a fonte. Você pode dar uma olhada aqui (attrs.xml) .

Você pode definir atributos no <resources>elemento superior ou dentro de um <declare-styleable>elemento. Se eu vou usar um attr em mais de um lugar, eu o coloco no elemento raiz. Observe que todos os atributos compartilham o mesmo espaço para nome global. Isso significa que, mesmo que você crie um novo atributo dentro de um <declare-styleable>elemento, ele poderá ser usado fora dele e você não poderá criar outro atributo com o mesmo nome de um tipo diferente.

Um <attr>elemento possui dois atributos xml namee format. namepermite chamá-lo de algo e é assim que você acaba se referindo a ele no código, por exemplo R.attr.my_attribute,. O formatatributo pode ter valores diferentes, dependendo do 'tipo' de atributo que você deseja.

  • referência - se referenciar outro ID de recurso (por exemplo, "@ color / my_color", "@ layout / my_layout")
  • cor
  • boleano
  • dimensão
  • flutuador
  • inteiro
  • corda
  • fração
  • enum - normalmente definido implicitamente
  • flag - normalmente definido implicitamente

Você pode definir o formato para vários tipos usando |, por exemplo format="reference|color",.

enum atributos podem ser definidos da seguinte maneira:

<attr name="my_enum_attr">
  <enum name="value1" value="1" />
  <enum name="value2" value="2" />
</attr>

flag Os atributos são semelhantes, exceto que os valores precisam ser definidos para que possam ser agrupados:

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

Além dos atributos, existe o <declare-styleable>elemento Isso permite definir atributos que uma exibição personalizada pode usar. Você faz isso especificando um <attr>elemento; se ele foi definido anteriormente, você não especifica o format. Se você deseja reutilizar um atributo android, por exemplo, android: gravity, faça isso da nameseguinte maneira.

Um exemplo de uma visualização personalizada <declare-styleable>:

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

Ao definir seus atributos customizados em XML em sua visualização customizada, você precisa fazer algumas coisas. Primeiro você deve declarar um espaço para nome para encontrar seus atributos. Você faz isso no elemento de layout raiz. Normalmente existe apenas xmlns:android="http://schemas.android.com/apk/res/android". Agora você também deve adicionar xmlns:whatever="http://schemas.android.com/apk/res-auto".

Exemplo:

<?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>

Por fim, para acessar esse atributo customizado, você normalmente o faz no construtor de sua visualização customizada, como a seguir.

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

O fim. :)

Rich Schuler
fonte
14
Aqui está um exemplo de projeto demonstrando atributos personalizados para uso com um costume View: github.com/commonsguy/cw-advandroid/tree/master/Views/…
CommonsWare
7
Se você estiver usando atributos personalizados de um projeto de biblioteca: consulte esta pergunta: stackoverflow.com/questions/5819369/… - Parece funcionar se você usar xmlns:my="http://schemas.android.com/apk/lib/my.namespace"- sem copiar attrs.xml. Observe que o caminho do URI do namespace deve ser / apk / * lib * not / apk / res.
thom_nic
2
@ThomNichols o apk/libtruque não funcionou para mim em atributos personalizados com formato de referência de um projeto de biblioteca. O que fez trabalho foi o uso apk/res-auto, como sugerido na stackoverflow.com/a/13420366/22904 logo abaixo e também no stackoverflow.com/a/10217752
Giulio Piancastelli
1
Citando @Qberticus: "os atributos do sinalizador são semelhantes, exceto que os valores precisam ser definidos para que possam ser agrupados". Na minha opinião, isso meio que subestima a principal diferença entre enume flag: o primeiro nos permite escolher um e apenas um valor, o último nos permite combinar vários. Escrevi uma resposta mais longa em uma pergunta semelhante aqui e, tendo encontrado essa pergunta, achei que iria vincular a ela.
Rad Haring
5
a.recycle()é muito importante aqui para liberar memória
Tash Pemhiwa
87

A resposta do Qberticus é boa, mas falta um detalhe útil. Se você os estiver implementando em uma biblioteca, substitua:

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

com:

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

Caso contrário, o aplicativo que usa a biblioteca terá erros de tempo de execução.

Neil Miller
fonte
3
Isso foi adicionado recentemente ... acho que há algumas semanas atrás. Certamente foi acrescentado muito tempo depois que Qberticus escreveu sua resposta.
ArtOfWarfare 19/11/12
12
Eu acho que é mais antigo que isso, mas certamente foi adicionado muito tempo depois que Qberticus escreveu sua resposta. Não o culpo, apenas adicionando um detalhe útil.
Neil Miller
11
Atualizei a resposta do Qbericus para usar o apk / res-auto para economizar confusão.
Intrications
15

A resposta acima cobre tudo em grande detalhe, além de algumas coisas.

Primeiro, se não houver estilos, a (Context context, AttributeSet attrs)assinatura do método será usada para instanciar a preferência. Nesse caso, use apenas context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)para obter o TypedArray.

Em segundo lugar, não aborda como lidar com os recursos das plurais (sequências de quantidade). Isso não pode ser tratado com o TypedArray. Aqui está um trecho de código do meu SeekBarPreference que define o resumo da preferência, formatando seu valor de acordo com o valor da preferência. Se o xml da preferência definir android: summary como uma sequência de texto ou uma sequência de caracteres, o valor da preferência será formatado na sequência de caracteres (ele deve ter% d, para selecionar o valor). Se android: summary estiver definido como um recurso de plaurals, isso será usado para formatar o 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);
}

  • Isso é apenas um exemplo, no entanto, se você quiser tentar o resumo na tela de preferências, precisará chamar notifyChanged()o onDialogClosedmétodo da preferência .
Steve Waring
fonte
5

A abordagem tradicional é cheia de código padrão e manipulação de recursos desajeitada. Foi por isso que criei a estrutura Spyglass . Para demonstrar como funciona, aqui está um exemplo que mostra como criar uma exibição personalizada que exibe um título de String.

Etapa 1: Crie uma classe de exibição 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);
    }
}

Etapa 2: defina um atributo de sequência no values/attrs.xmlarquivo de recurso:

<resources>
    <declare-styleable name="CustomView">
        <attr name="title" format="string"/>
    </declare-styleable>
</resources>

Etapa 3: aplique a @StringHandleranotação ao setTitlemétodo para informar à estrutura do Spyglass para rotear o valor do atributo para esse método quando a exibição for inflada.

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

Agora que sua classe possui uma anotação Spyglass, a estrutura Spyglass a detectará em tempo de compilação e gerará automaticamente a CustomView_SpyglassCompanionclasse.

Etapa 4: use a classe gerada no initmétodo da visualização 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();
}

É isso aí. Agora, quando você instancia a classe a partir do XML, o companheiro Spyglass interpreta os atributos e faz a chamada de método necessária. Por exemplo, se inflarmos o seguinte layout, setTitleele será chamado com "Hello, World!"o 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>

A estrutura não se limita aos recursos de cadeia de caracteres e possui muitas anotações diferentes para manipular outros tipos de recursos. Ele também possui anotações para definir valores padrão e para passar valores de espaço reservado, se seus métodos tiverem vários parâmetros.

Dê uma olhada no repositório do Github para obter mais informações e exemplos.

Helios
fonte
Você pode obter o mesmo com a Ligação de dados do Google - se não houver ligação de atributo para um atributo específico, o GDB tentará encontrar o método set * e o usará. Nesse caso, você teria que escrever, digamos android:title="@{&quot;Hello, world!&quot;}".
susto
0

se você omitir o formatatributo do attrelemento, poderá usá-lo para referenciar uma classe a partir de layouts XML.

  • exemplo de attrs.xml .
  • O Android Studio entende que a classe está sendo referenciada em XML
    • ie
      • Refactor > Rename trabalho
      • Find Usages trabalho
      • e assim por diante...

não especifique um formatatributo em ... / 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-o em algum arquivo de layout ... / 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>

analise a classe no seu código de inicialização de exibição ... / 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()
            }
        }
    }
Eric
fonte