Declarando um elemento personalizado da UI do Android usando XML

Respostas:

840

O Android Developer Guide possui uma seção chamada Building Custom Components . Infelizmente, a discussão dos atributos XML abrange apenas declarar o controle dentro do arquivo de layout e não lidar com os valores dentro da inicialização da classe. Os passos são os seguintes:

1. Declarar atributos em values\attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyCustomView">
        <attr name="android:text"/>
        <attr name="android:textColor"/>            
        <attr name="extraInformation" format="string" />
    </declare-styleable>
</resources>

Observe o uso de um nome não qualificado na declare-styleabletag. Atributos do Android não padrão, como extraInformationnecessidade de ter seu tipo declarado. As tags declaradas na superclasse estarão disponíveis nas subclasses sem precisar ser redeclaradas.

2. Crie construtores

Como existem dois construtores que usam um AttributeSetpara inicialização, é conveniente criar um método de inicialização separado para os construtores chamarem.

private void init(AttributeSet attrs) { 
    TypedArray a=getContext().obtainStyledAttributes(
         attrs,
         R.styleable.MyCustomView);

    //Use a
    Log.i("test",a.getString(
         R.styleable.MyCustomView_android_text));
    Log.i("test",""+a.getColor(
         R.styleable.MyCustomView_android_textColor, Color.BLACK));
    Log.i("test",a.getString(
         R.styleable.MyCustomView_extraInformation));

    //Don't forget this
    a.recycle();
}

R.styleable.MyCustomViewé um int[]recurso gerado automaticamente , em que cada elemento é o ID de um atributo. Os atributos são gerados para cada propriedade no XML anexando o nome do atributo ao nome do elemento. Por exemplo, R.styleable.MyCustomView_android_textcontém o android_textatributo para MyCustomView. Os atributos podem ser recuperados TypedArrayusando várias getfunções. Se o atributo não estiver definido no definido no XML, nullserá retornado. Exceto, é claro, se o tipo de retorno for um primitivo; nesse caso, o segundo argumento será retornado.

Se você não deseja recuperar todos os atributos, é possível criar essa matriz manualmente.O ID para os atributos padrão do Android está incluído android.R.attr, enquanto os atributos deste projeto estão R.attr.

int attrsWanted[]=new int[]{android.R.attr.text, R.attr.textColor};

Observe que você não deve usar nada android.R.styleable, pois esse segmento pode mudar no futuro. Ainda está na documentação, pois é útil visualizar todas essas constantes em um único local.

3. Use-o em arquivos de layout, como layout\main.xml

Inclua a declaração do espaço para nome xmlns:app="http://schemas.android.com/apk/res-auto"no elemento xml de nível superior. Os espaços para nome fornecem um método para evitar conflitos que às vezes ocorrem quando esquemas diferentes usam os mesmos nomes de elemento (consulte este artigo para obter mais informações). O URL é simplesmente uma maneira de identificar esquemas exclusivamente - nada precisa ser hospedado nesse URL . Se isso não parece estar fazendo nada, é porque você realmente não precisa adicionar o prefixo do espaço para nome, a menos que precise resolver um conflito.

<com.mycompany.projectname.MyCustomView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    android:text="Test text"
    android:textColor="#FFFFFF"
    app:extraInformation="My extra information"
/> 

Faça referência à visualização customizada usando o nome completo.

Amostra Android LabelView

Se você quiser um exemplo completo, veja a amostra de exibição de rótulo do Android.

LabelView.java

 TypedArray a=context.obtainStyledAttributes(attrs, R.styleable.LabelView);
 CharSequences=a.getString(R.styleable.LabelView_text);

attrs.xml

<declare-styleable name="LabelView">
    <attr name="text"format="string"/>
    <attr name="textColor"format="color"/>
    <attr name="textSize"format="dimension"/>
</declare-styleable>

custom_view_1.xml

<com.example.android.apis.view.LabelView
    android:background="@drawable/blue"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    app:text="Blue" app:textSize="20dp"/>

Isso está contido em um LinearLayoutcom um atributo de espaço para nome:xmlns:app="http://schemas.android.com/apk/res-auto"

Ligações

Casebash
fonte
14
Gostaria de acrescentar que, se o seu elemento raiz exigir seu espaço para nome personalizado, você precisará adicionar o espaço para nome padrão do Android e o seu personalizado, caso contrário, poderá ocorrer erros de compilação.
Chase
11
Esta resposta é o recurso mais claro da Internet em parâmetros XML personalizados que consegui encontrar. Obrigado Casebash.
Artem Russakovskii
2
por alguma razão, o editor visual se recusa a usar o valor do texto escrito para o android: texto, mas o dispositivo o utiliza muito bem. Por quê ?
desenvolvedor android
2
@androiddeveloper Parece que o editor Eclipse se recusa a usar os valores para todos os atributos android: Gostaria de saber se é um recurso ou bug
deej
4
Qual é o objetivo do xmlns: namespace do aplicativo e res-auto?
IgorGanapolsky
91

Ótima referência. Obrigado! Uma adição a ele:

Se houver um projeto de biblioteca incluído que tenha declarado atributos personalizados para uma exibição personalizada, você deverá declarar o namespace do projeto, não o da biblioteca. Por exemplo:

Dado que a biblioteca possui o pacote "com.example.library.customview" e o projeto de trabalho possui o pacote "com.example.customview", então:

Não funcionará (mostra o erro "erro: nenhum identificador de recurso encontrado para o atributo 'newAttr' no pacote 'com.example.library.customview'"):

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.library.customview"
        android:id="@+id/myView"
        app:newAttr="value" />

Funcionará:

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.customview"
        android:id="@+id/myView"
        app:newAttr="value" />
Andy
fonte
47
Isso foi corrigido na visualização do ADT 17. Para usar o espaço de nomes do aplicativo na biblioteca declare xmlns:app="http://schemas.android.com/apk/res-auto"Ver comentário 57 em code.google.com/p/android/issues/detail?id=9656
nmr
2
Incluindo o seu espaço de nomes personalizado agora retorna um erroSuspicious namespace: Did you mean http://schemas.android.com/apk/res-auto
Ben Wilkinson
o espaço para nome personalizado termina em res-auto, porque estamos usando o Android Studio e o Gradle. Caso contrário (por exemplo, algumas versões do Eclipse) que normalmente acabaria em lib / [seu nome do pacote]
Universo
o espaço para nome personalizado termina res-autoporque estamos usando o Android Studio e o Gradle. Caso contrário (por exemplo, algumas versões do Eclipse), geralmente terminaria lib/[your package name]. iehttp://schemas.android.com/apk/lib/[your package name]
Universo
27

Além da resposta mais votada.

getStyledAttributes ()

Quero adicionar algumas palavras sobre o uso de obtençãoStyledAttributes (), quando criamos uma exibição personalizada usando atributos predefinidos android: xxx. Especialmente quando usamos TextAppearance.
Como foi mencionado em "2. Criando Construtores", a visualização customizada obtém o AttributeSet na sua criação. Uso principal que podemos ver no código fonte do TextView (API 16).

final Resources.Theme theme = context.getTheme();

// TextAppearance is inspected first, but let observe it later

TypedArray a = theme.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.TextView, defStyle, 0);

int n = a.getIndexCount();
for (int i = 0; i < n; i++) 
{
    int attr = a.getIndex(i);
    // huge switch with pattern value=a.getXXX(attr) <=> a.getXXX(a.getIndex(i))
}
a.recycle();

O que podemos ver aqui?
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
O conjunto de atributos é processado por tema, de acordo com a documentação. Os valores dos atributos são compilados passo a passo. Os primeiros atributos são preenchidos a partir do tema, os valores são substituídos pelos valores do estilo e, finalmente, os valores exatos do XML para a instância de exibição especial substituem outros.
Matriz de atributos solicitados - com.android.internal.R.styleable.TextView
É uma matriz comum de constantes. Se estamos solicitando atributos padrão, podemos construir essa matriz manualmente.

O que não é mencionado na documentação - ordem dos elementos TypedArray do resultado.
Quando a visualização customizada é declarada em attrs.xml, constantes especiais para índices de atributos são geradas. E podemos extrair valores desta maneira:a.getString(R.styleable.MyCustomView_android_text) . Mas para o manual int[]não há constantes. Suponho que getXXXValue (arrayIndex) funcione bem.

E outra pergunta é: "Como podemos substituir constantes internas e solicitar atributos padrão?" Podemos usar android.R.attr. * Valores.

Portanto, se quisermos usar o atributo TextAppearance padrão no modo de exibição personalizado e ler seus valores no construtor, podemos modificar o código do TextView desta maneira:

ColorStateList textColorApp = null;
int textSize = 15;
int typefaceIndex = -1;
int styleIndex = -1;

Resources.Theme theme = context.getTheme();

TypedArray a = theme.obtainStyledAttributes(attrs, R.styleable.CustomLabel, defStyle, 0);
TypedArray appearance = null;
int apResourceId = a.getResourceId(R.styleable.CustomLabel_android_textAppearance, -1);
a.recycle();
if (apResourceId != -1)
{
    appearance = 
        theme.obtainStyledAttributes(apResourceId, new int[] { android.R.attr.textColor, android.R.attr.textSize, 
            android.R.attr.typeface, android.R.attr.textStyle });
}
if (appearance != null)
{
    textColorApp = appearance.getColorStateList(0);
    textSize = appearance.getDimensionPixelSize(1, textSize);
    typefaceIndex = appearance.getInt(2, -1);
    styleIndex = appearance.getInt(3, -1);

    appearance.recycle();
}

Onde CustomLabel está definido:

<declare-styleable name="CustomLabel">
    <!-- Label text. -->
    <attr name="android:text" />
    <!-- Label text color. -->
    <attr name="android:textColor" />
    <!-- Combined text appearance properties. -->
    <attr name="android:textAppearance" />
</declare-styleable>

Talvez eu esteja enganado de alguma maneira, mas a documentação do Android em obtençãoStyledAttributes () é muito ruim.

Estendendo o componente padrão da interface do usuário

Ao mesmo tempo, podemos apenas estender o componente padrão da interface do usuário, usando todos os seus atributos declarados. Essa abordagem não é tão boa, porque o TextView, por exemplo, declara muitas propriedades. E será impossível implementar a funcionalidade completa no onMeasure () e noDraw () substituídos.

Mas podemos sacrificar a ampla reutilização teórica do componente personalizado. Diga "Eu sei exatamente quais recursos vou usar" e não compartilhe código com ninguém.

Então podemos implementar o construtor CustomComponent(Context, AttributeSet, defStyle). Após a chamada super(...), todos os atributos serão analisados ​​e disponibilizados pelos métodos getter.

yuriy.weiss
fonte
atributos predefinidos do android: xxx funcionam no eclipse gui designer?
28813 deej
Esses atributos são reconhecidos pelo plug-in do Eclipse ADT no editor de propriedades. Eu posso ver os padrões do meu estilo, se alguns valores não estiverem definidos. E não esqueça de adicionar a anotação @RemoteView à sua turma.
yuriy.weiss
Não pode fazê-lo funcionar. O Eclipse continua carregando nulos para getText e lançando android.content.res.Resources $ NotFoundException para getResourceId, embora o aplicativo funcione bem em um dispositivo.
28613 deej
Desculpe, não posso ajudá-lo. Eu criei apenas um projeto de demonstração para testar possibilidades e não encontrei esses erros.
Yuriy.weiss 29/03
Isso é muito melhor do que mapear atributos customizados de uma visualização customizada para os atributos internos das visualizações integradas.
samis 11/07
13

Parece que o Google atualizou sua página de desenvolvedor e adicionou vários treinamentos lá.

Um deles trata da criação de visualizações personalizadas e pode ser encontrado aqui

mitch000001
fonte
5

Muito obrigado pela primeira resposta.

Quanto a mim, tive apenas um problema com isso. Ao inflar minha visão, tive um bug: java.lang.NoSuchMethodException: MyView (Context, Attributes)

Eu resolvi isso criando um novo construtor:

public MyView(Context context, AttributeSet attrs) {
     super(context, attrs);
     // some code
}

Espero que isso ajude!

user2346922
fonte
0

Você pode incluir qualquer arquivo de layout em outro arquivo de layout, como

             <RelativeLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="30dp" >

                <include
                    android:id="@+id/frnd_img_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_imagefile"/>

                <include
                    android:id="@+id/frnd_video_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_video_lay" />

                <ImageView
                    android:id="@+id/downloadbtn"
                    android:layout_width="30dp"
                    android:layout_height="30dp"
                    android:layout_centerInParent="true"
                    android:src="@drawable/plus"/>
            </RelativeLayout>

aqui, os arquivos de layout na tag include são outros arquivos de layout .xml na mesma pasta res.

Akshay Paliwal
fonte
Eu tentei isso, o problema que tenho com isso é que o layout incluído não pode ser 'adaptado', não pode criar genéricos. Por exemplo, quando incluo um botão de maneira semelhante, se eu tentar definir o texto no xml, ele funciona.
CFL