Não é incomum que desenvolvedores precisem de um CEBOLA componente que não é fornecido pela plataforma a que se destina ou é, de fato, fornecido, mas não possui uma determinada propriedade ou comportamento. A resposta para ambos os cenários é um componente de IU personalizado.
O modelo de IU do Android é inerentemente personalizável, oferecendo os meios de Personalização Android , testando , e a capacidade de criar componentes de IU personalizados de varias maneiras:
Herdar um componente existente (ou seja, TextView
, ImageView
, etc.) e adicionar / substituir a funcionalidade necessária. Por exemplo, um CircleImageView
que herda ImageView
, substituindo o onDraw()
função para restringir a imagem exibida a um círculo e adicionar um loadFromFile()
função para carregar uma imagem da memória externa.
Crie um componente composto de vários componentes. Essa abordagem geralmente tira proveito de Layouts para controlar como os componentes são organizados na tela. Por exemplo, um LabeledEditText
que herda LinearLayout
com orientação horizontal e contém um TextView
atuando como um rótulo e um EditText
atuando como um campo de entrada de texto.
Esta abordagem também pode fazer uso da anterior, ou seja, os componentes internos podem ser nativos ou personalizados.
A abordagem mais versátil e complexa é criar um componente desenhado por si mesmo . Nesse caso, o componente herdaria o genérico View
classe e funções de substituição como onMeasure()
para determinar seu layout, onDraw()
para exibir seu conteúdo, etc. Componentes criados dessa forma geralmente dependem muito do Android API de desenho 2D .
CalendarView
Android fornece um nativo CalendarView
componente . Ele tem um bom desempenho e fornece a funcionalidade mínima esperada de qualquer componente do calendário, exibindo um mês inteiro e destacando o dia atual. Alguns podem dizer que parece bom também, mas apenas se você estiver procurando um visual nativo e não tiver interesse em personalizar sua aparência de qualquer maneira.
Por exemplo, o CalendarView
O componente não oferece nenhuma maneira de alterar a forma como um determinado dia é marcado ou a cor de fundo a ser usada. Também não há como adicionar qualquer texto ou gráfico personalizado, para marcar uma ocasião especial, por exemplo. Resumindo, o componente se parece com isto e quase nada pode ser alterado:
padrão decorativo é comumente associado com
CalendarView
em AppCompact.Light
tema.
Então, como alguém faz para criar sua própria visualização de calendário? Qualquer uma das abordagens acima funcionaria. No entanto, a praticidade geralmente descarta a terceira opção (gráficos 2D) e nos deixa com os outros dois métodos, e vamos empregar uma mistura de ambos neste artigo.
Para acompanhar, você pode encontrar o código-fonte Aqui .
Primeiro, vamos começar com a aparência do componente. Para manter as coisas simples, vamos exibir os dias em uma grade e, na parte superior, o nome do mês junto com os botões 'próximo mês' e 'mês anterior'.
Visualização de calendário personalizada.
Este layout é definido no arquivo control_calendar.xml
, como segue. Observe que algumas marcações repetitivas foram abreviadas com ...
:
... Repeat for MON - SAT.
O layout anterior pode ser incluído como está em um Activity
ou um Fragment
e vai funcionar bem. Mas encapsulá-lo como um componente de UI autônomo evitará a repetição do código e permitirá um design modular, onde cada módulo lida com uma responsabilidade.
Nosso componente de IU será um LinearLayout
, para corresponder à raiz do arquivo de layout XML. Observe que apenas as partes importantes são mostradas no código. A implementação do componente reside em CalendarView.java
:
public class CalendarView extends LinearLayout { // internal components private LinearLayout header; private ImageView btnPrev; private ImageView btnNext; private TextView txtDate; private GridView grid; public CalendarView(Context context) { super(context); initControl(context); } /** * Load component XML layout */ private void initControl(Context context) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.control_calendar, this); // layout is inflated, assign local variables to components header = (LinearLayout)findViewById(R.id.calendar_header); btnPrev = (ImageView)findViewById(R.id.calendar_prev_button); btnNext = (ImageView)findViewById(R.id.calendar_next_button); txtDate = (TextView)findViewById(R.id.calendar_date_display); grid = (GridView)findViewById(R.id.calendar_grid); } }
O código é bastante simples. Ao ser criado, o componente infla o layout XML e, quando feito, atribui os controles internos às variáveis locais para facilitar o acesso posteriormente.
Para fazer esse componente realmente se comportar como uma visualização de calendário, é necessária alguma lógica de negócios. Pode parecer complicado no início, mas realmente não há muito a fazer. Vamos decompô-lo:
A visualização do calendário tem sete dias de largura e é garantido que todos os meses começarão em algum lugar na primeira linha.
Primeiro, precisamos descobrir em que posição o mês começa e, em seguida, preencher todas as posições anteriores com os números do mês anterior (30, 29, 28 .. etc.) até chegarmos à posição 0.
Em seguida, preenchemos os dias do mês atual (1, 2, 3 ... etc).
exemplos de orçamento de capital com soluções
Depois disso, vêm os dias do próximo mês (novamente, 1, 2, 3 .. etc), mas desta vez apenas preencheremos as posições restantes na (s) última (s) linha (s) da grade.
O diagrama a seguir ilustra essas etapas:
Lógica de negócios de exibição de calendário personalizado.
A largura da grade já está especificada em sete células, denotando um calendário semanal, mas e a altura? O maior tamanho da grade pode ser determinado pelo pior cenário de um mês de 31 dias começando em um sábado, que é a última célula na primeira linha, e precisará de mais 5 linhas para exibir por completo. Portanto, configurar o calendário para exibir seis linhas (totalizando 42 dias) será suficiente para lidar com todos os casos.
Mas nem todos os meses têm 31 dias! Podemos evitar complicações decorrentes disso usando a funcionalidade de data integrada do Android, evitando a necessidade de descobrir o número de dias nós mesmos.
Conforme mencionado anteriormente, as funcionalidades de data fornecidas pelo Calendar
classe torna a implementação bastante direta. Em nosso componente, o updateCalendar()
função implementa esta lógica:
private void updateCalendar() { ArrayList cells = new ArrayList(); Calendar calendar = (Calendar)currentDate.clone(); // determine the cell for current month's beginning calendar.set(Calendar.DAY_OF_MONTH, 1); int monthBeginningCell = calendar.get(Calendar.DAY_OF_WEEK) - 1; // move calendar backwards to the beginning of the week calendar.add(Calendar.DAY_OF_MONTH, -monthBeginningCell); // fill cells (42 days calendar as per our business logic) while (cells.size() 4. Customizável no coração
Como o componente responsável por exibir os dias individuais é um GridView
, um bom lugar para customizar como os dias são exibidos é o Adapter
, já que ele é responsável por manter os dados e aumentar as visualizações para células individuais da grade.
o que é powerpivot no excel
Para este exemplo, exigiremos o seguinte de nosso CalendearView
:
- Os dias atuais deveriam estar emtexto em negrito azul.
- Dias fora do mês atual devem seracinzentado.
- Os dias com um evento devem exibir um ícone especial.
- O cabeçalho do calendário deve mudar de cor dependendo da estação (verão, outono, inverno, primavera).
Os três primeiros requisitos são simples de atingir, alterando os atributos de texto e recursos de fundo. Vamos implementar um CalendarAdapter
para realizar esta tarefa. É simples o suficiente para ser uma classe membro em CalendarView
. Substituindo o getView()
função, podemos cumprir os requisitos acima:
@Override public View getView(int position, View view, ViewGroup parent) { // day in question Date date = getItem(position); // today Date today = new Date(); // inflate item if it does not exist yet if (view == null) view = inflater.inflate(R.layout.control_calendar_day, parent, false); // if this day has an event, specify event image view.setBackgroundResource(eventDays.contains(date)) ? R.drawable.reminder : 0); // clear styling view.setTypeface(null, Typeface.NORMAL); view.setTextColor(Color.BLACK); if (date.getMonth() != today.getMonth() || date.getYear() != today.getYear()) { // if this day is outside current month, grey it out view.setTextColor(getResources().getColor(R.color.greyed_out)); } else if (date.getDate() == today.getDate()) { // if it is today, set it to blue/bold view.setTypeface(null, Typeface.BOLD); view.setTextColor(getResources().getColor(R.color.today)); } // set text view.setText(String.valueOf(date.getDate())); return view; }
O requisito de design final exige um pouco mais de trabalho. Primeiro, vamos adicionar as cores para as quatro estações em /res/values/colors.xml
:
#44eebd82 #44d8d27e #44a1c1da #448da64b
Então, vamos usar uma matriz para definir a estação para cada mês (assumindo o hemisfério norte, para simplificar; desculpe, Austrália!). Em CalendarView
adicionamos as seguintes variáveis de membro:
// seasons' rainbow int[] rainbow = new int[] { R.color.summer, R.color.fall, R.color.winter, R.color.spring }; int[] monthSeason = new int[] {2, 2, 3, 3, 3, 0, 0, 0, 1, 1, 1, 2};
Desta forma, a seleção de uma cor apropriada é feita selecionando a estação apropriada (monthSeason[currentMonth]
) e, em seguida, escolhendo a cor correspondente (rainbow[monthSeason[currentMonth]
), isso é adicionado a updateCalendar()
para garantir que a cor apropriada seja selecionada sempre que o calendário for alterado.
// set header color according to current season int month = currentDate.get(Calendar.MONTH); int season = monthSeason[month]; int color = rainbow[season]; header.setBackgroundColor(getResources().getColor(color));
Com isso, obtemos o seguinte resultado:
A cor do cabeçalho muda de acordo com a estação.
Nota importante devido à forma HashSet
compara objetos, a verificação acima eventDays.contains(date)
em updateCalendar()
não produzirá true para objetos de data a menos que sejam exatamente idênticos. Ele não executa nenhuma verificação especial para Date
tipo de dados. Para contornar isso, esta verificação foi substituída pelo seguinte código:
for (Date eventDate : eventDays) { if (eventDate.getDate() == date.getDate() && eventDate.getMonth() == date.getMonth() && eventDate.getYear() == date.getYear()) { // mark this day for event view.setBackgroundResource(R.drawable.reminder); break; } }
5. Parece feio em tempo de design
A escolha do Android para marcadores em tempo de design pode ser questionável. Felizmente, o Android na verdade instancia nosso componente para renderizá-lo no designer de interface do usuário e podemos explorar isso chamando updateCalendar()
no construtor do componente. Dessa forma, o componente realmente fará sentido em tempo de design.

Se a inicialização do componente exigir muito processamento ou carregar muitos dados, isso pode afetar o desempenho do IDE. Nesse caso, o Android fornece uma função bacana chamada isInEditMode()
que pode ser usado para limitar a quantidade de dados usados quando o componente é realmente instanciado no designer de IU. Por exemplo, se houver muitos eventos a serem carregados no CalendarView
, podemos usar isInEditMode()
dentro de updateCalendar()
função para fornecer uma lista de eventos vazia / limitada no modo de design e carregar a real caso contrário.
6. Invocando o componente
O componente pode ser incluído em arquivos de layout XML (um exemplo de uso pode ser encontrado em activity_main.xml
):
HashSet events = new HashSet(); events.add(new Date()); CalendarView cv = ((CalendarView)findViewById(R.id.calendar_view)); cv.updateCalendar(events);
E recuperado para interagir assim que o layout for carregado:
HashSet
O código acima cria um CalendarView
de eventos, adiciona o dia atual a ele e o passa para CalendarView
. Como resultado, o CalendarView
exibirá o dia atual em negrito azul e também colocará o marcador do evento nele:
CalendarView
exibindo um evento
7. Adicionando Atributos
Outro recurso fornecido pelo Android é atribuir atributos a um componente personalizado. Isso permite Desenvolvedores Android usando o componente para selecionar configurações por meio do XML de layout e ver o resultado imediatamente no designer de IU, ao invés de ter que esperar para ver como o dateFormat
parece em tempo de execução. Vamos adicionar a capacidade de alterar a exibição do formato de data no componente, por exemplo, para soletrar o nome completo do mês em vez da abreviação de três letras.
Para fazer isso, as seguintes etapas são necessárias:
- Declare o atributo. Vamos chamá-lo de
string
e dar /res/values/attrs.xml
tipo de dados. Adicione-o a 'MMMM yyyy'
:
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CalendarView); dateFormat = ta.getString(R.styleable.CalendarView_dateFormat);
- Use o atributo no layout que está usando o componente e atribua a ele o valor
CalendarView
:
Fragment
- Por fim, faça com que o componente use o valor do atributo:
Activity
] Construa o projeto e você notará as mudanças de data exibidas no Designer de interface do usuário para usar o nome completo do mês, como “julho de 2015”. Experimente fornecer valores diferentes e veja o que acontece.
Alterando o // long-pressing a day grid.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView view, View cell, int position, long id) { // handle long-press if (eventHandler == null) return false; Date date = view.getItemAtPosition(position); eventHandler.onDayLongPress(date); return true; } });
atributos.
8. Interagindo com o componente
Você já tentou pressionar em um dia específico? Os elementos da IU internos em nosso componente ainda se comportam da maneira normal esperada e dispararão eventos em resposta às ações do usuário. Então, como lidamos com esses eventos?
usando macros em planilhas do google
A resposta envolve duas partes:
- Capture eventos dentro do componente e
- Reportar eventos ao pai do componente (pode ser um
eventHandler
, um CalendarView
ou mesmo outro componente).
A primeira parte é bastante direta. Por exemplo, para lidar com itens de grade pressionados longamente, atribuímos um ouvinte correspondente em nossa classe de componente:
public interface EventHandler { void onDayLongPress(Date date); }
Existem vários métodos para relatar eventos. Um direto e simples é copiar a maneira como o Android faz: ele fornece uma interface para os eventos do componente que é implementada pelo pai do componente (setEventHandler()
no trecho de código acima).
As funções da interface podem receber qualquer dado relevante para o aplicativo. Em nosso caso, a interface precisa expor um manipulador de eventos, que é a data do dia pressionado. A seguinte interface é definida em @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); HashSet events = new HashSet(); events.add(new Date()); CalendarView cv = ((CalendarView)findViewById(R.id.calendar_view)); cv.updateCalendar(events); // assign event handler cv.setEventHandler(new CalendarView.EventHandler() { @Override public void onDayLongPress(Date date) { // show returned day DateFormat df = SimpleDateFormat.getDateInstance(); Toast.makeText(MainActivity.this, df.format(date), LENGTH_SHORT).show(); } }); }
:
GridView
A implementação fornecida pelo pai pode ser fornecida à visualização do calendário por meio de um onDayLongPress()
. Aqui está um exemplo de uso de `MainActivity.java ':
Intents
Pressionar um dia longamente irá disparar um evento de pressionar longamente que é capturado e manipulado pelo BroadcastReceivers
e relatado pelo telefone Activity
na implementação fornecida, que, por sua vez, mostrará a data do dia pressionado na tela:

Outra maneira mais avançada de lidar com isso é usando o Android Service
e Activity
. Isso é particularmente útil quando vários componentes precisam ser notificados sobre o evento da agenda. Por exemplo, se pressionar um dia no calendário requer que um texto seja exibido em um EventHandler
e um arquivo a ser baixado por um fundo Service
.
Usar a abordagem anterior exigirá o Intent
para fornecer um Activity
para o componente, manipulando o evento e, em seguida, passando-o para o Service
. Em vez disso, fazer com que o componente transmita um BroadcastReceivers
e tanto o Activity
e Service
aceitando-o por conta própria isInEditMode()
não apenas torna a vida mais fácil, mas também ajuda a desacoplar o
|_+_|
e o |_+_|
em questão. Conclusão
Contemple o incrível poder de personalização do Android! Tweet Então, é assim que você cria seu próprio componente personalizado em algumas etapas simples:
o que é um compilador c ++
- Crie o layout XML e estilize-o para atender às suas necessidades.
- Derive sua classe de componente do componente pai apropriado, de acordo com seu layout XML.
- Adicione a lógica de negócios do seu componente.
- Use atributos para permitir que os usuários modifiquem o comportamento do componente.
- Para tornar mais fácil o uso do componente no designer de IU, use Android
|_+_|
função.
Neste artigo, criamos uma visualização de calendário como exemplo, principalmente porque a visualização de calendário de ações está, em muitos aspectos, ausente. Mas você não está de forma alguma limitado quanto ao tipo de componentes que pode criar. Você pode usar a mesma técnica para criar o que precisar, o céu é o limite!
Obrigado por ler este guia, desejo-lhe boa sorte em seus esforços de codificação!