Este artigo é um conjunto de pequenos cubos de açúcar para um projeto Android, que cheguei no devido tempo e que foram úteis. Algumas coisas podem não ser as soluções ideais, mas podem ser úteis para você, assim como foram úteis para mim ao mesmo tempo.
Aplicação e Brinde
A primeira coisa que sempre pode ser útil e, às vezes, é necessária em qualquer ponto do programa é um link para o Aplicativo. Isso é resolvido por uma classe simples, cujo link está registrado no AndroidManifest.
class App : Application() { init { APP = this } companion object { lateinit var APP: App } }
Graças a isso, sempre há acesso ao contexto de todo o aplicativo e você pode obter linhas / recursos de qualquer lugar. E pelo menos isso é necessário para o seguinte grão de açúcar:
fun Toast(messageId: Int) { Toast.makeText(App.APP, messageId, Toast.LENGTH_LONG).show() } fun Toast(message: String) { Toast.makeText(App.APP, message, Toast.LENGTH_LONG).show() }
Um pouco, mas graças ao Kotlin e ao fato de termos acesso ao contexto, agora você pode ligar para o Toast em breve e de forma concisa de qualquer lugar do aplicativo. Para que o método seja acessível em qualquer lugar, ele pode ser marcado em um arquivo sem especificar a classe raiz.
Quem está na tela?
Programas com uma atividade estão ganhando popularidade. Mas para nossa arquitetura, foi decidido usar várias atividades. Pelo menos para separar a lógica de autorização e a parte principal do aplicativo. Com o tempo, houve a necessidade de entender se a tela está visível e qual parte do aplicativo é. E no futuro, também era necessário obter seqüências de caracteres no código do idioma do aplicativo. Mas as primeiras coisas primeiro. Para que nosso valor de APP não fique entediado sozinho, a empresa o fará:
var screenActivity: AppCompatActivity? = null var onScreen: Boolean = false
E então, criamos nossa classe base, herdada de AppCompatActivity:
open class BaseActivity : AppCompatActivity() { override fun onStart() { super.onStart() App.onScreen = true } override fun onStop() { super.onStop() if (App.screenActivity == this) { App.onScreen = false if (isFinishing()) App.screenActivity = null } } override fun onDestroy() { super.onDestroy() if (App.screenActivity == this) App.screenActivity = null } override fun onCreate(savedInstanceState: Bundle?) { App.screenActivity = this } override fun onRestart() { super.onRestart() App.screenActivity = this } }
Sim, para novos guias, você pode assinar o Ciclo de Vida com Atividade nos locais necessários. Mas nós temos o que temos.
Sequências localizadas
Existem funcionais de utilidade duvidosa, mas TK é TK. Para essa funcionalidade, eu atribuiria a escolha do idioma no aplicativo em vez do idioma do sistema. Por um longo tempo, existe um código que permite substituir programaticamente o idioma. Mas nos deparamos com um bug, que provavelmente se repete apenas conosco. A essência do erro é que, se você passar a linha pelo contexto do aplicativo, e não pelo contexto de Atividade, a linha será retornada aos códigos de idioma do sistema. E nem sempre é conveniente lançar o contexto de uma atividade. Os seguintes métodos vieram para o resgate:
fun getRes(): Resources = screenActivity?.resources ?: APP.resources fun getLocalizedString(stringId: Int): String = getRes().getString(stringId) fun getLocalizedString(stringId: Int, vararg formatArgs: Any?): String = getRes().getString(stringId, *formatArgs)
E agora, de qualquer lugar do aplicativo, podemos obter a string no local correto.
SharedPreferences
Como em todos os aplicativos, o nosso deve armazenar algumas configurações em SharedPreferences. E para simplificar a vida, foi inventada uma classe que esconde um pouco de lógica em si mesma. Para começar, um novo amigo foi adicionado à variável APP:
lateinit var settings: SharedPreferences
Ele é inicializado quando o aplicativo é iniciado e está sempre disponível para nós.
class PreferenceString(val key: String, val def: String = "", val store: SharedPreferences = App.settings, val listener: ModifyListener? = null) { var value: String get() { listener?.customGet() return store.getString(key, def) ?: def } set(value) { store.edit().putString(key, value).apply() listener?.customSet() } } interface ModifyListener { fun customGet() {} fun customSet() {} }
Obviamente, você precisará gerar essa classe para cada tipo de variável, mas poderá substituir o singleton por todas as configurações necessárias, por exemplo:
val PREF_LANGUAGE = PreferenceString("pref_language", "ru")
E agora você sempre pode consultar as configurações como um campo, e o carregamento / salvamento e a comunicação através do ouvinte serão ocultados.
Orientação e Tablet
Você já teve que apoiar as duas orientações? E como você determinou em qual orientação você está agora? Temos um método conveniente para isso, que novamente pode ser chamado de qualquer lugar e não se importa com o contexto:
fun isLandscape(): Boolean { return getRes().configuration.orientation == Configuration.ORIENTATION_LANDSCAPE }
Se você colocar valores / dimen.xml:
<bool name="isTablet">false</bool>
E em valores-grandes / dimen.xml:
<bool name="isTablet">true</bool>
Então você também pode criar um método:
fun isTablet(): Boolean { return getRes().getBoolean(R.bool.isTablet) }
Formato da data
Multithreading. Às vezes é uma palavra assustadora. Uma vez pegamos um bug muito estranho quando formamos linhas de datas em segundo plano. Descobriu-se que SimpleDateFormat não é seguro para threads. Portanto, nasceu o seguinte:
class ThreadSafeDateFormat(var pattern: String, val isUTC: Boolean = false, val locale: Locale = DEFAULT_LOCALE){ val dateFormatThreadLocal = object : ThreadLocal<SimpleDateFormat>(){ override fun initialValue(): SimpleDateFormat? { return SimpleDateFormat(pattern, locale) } } val formatter: SimpleDateFormat get() { val dateFormat = dateFormatThreadLocal.get() ?: SimpleDateFormat(pattern, locale) dateFormat.timeZone = if (isUTC) TimeZone.getTimeZone("UTC") else timeZone return dateFormat } }
E um exemplo de uso (sim, isso é novamente usado dentro do singleton):
private val utcDateSendSafeFormat = ThreadSafeDateFormat("yyyy-MM-dd", true) val utcDateSendFormat: SimpleDateFormat get() = utcDateSendSafeFormat.formatter
Para todo o aplicativo, nada mudou e o problema com os threads foi resolvido.
Textwatcher
E você nunca se incomodou que, se precisar capturar o texto digitado no EditText, precisará usar o TextWatcher e implementar 3 (!) Métodos. Não é crítico, mas não é conveniente. E tudo é decidido pela classe:
open class TextWatcherObject : TextWatcher{ override fun afterTextChanged(p0: Editable?) {} override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} }
Teclado
O que é sempre necessário. Você precisa mostrar imediatamente o teclado e, em algum momento, precisa escondê-lo. E então os dois métodos a seguir são necessários. No segundo caso, você precisa passar pela visualização raiz.
fun showKeyboard(view: EditText){ view.requestFocus(); (App.APP.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?) ?.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) } fun hideKeyboardFrom(view: View) { (App.APP.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?) ?.hideSoftInputFromWindow(view.windowToken, 0) }
E, talvez alguém venha a calhar, uma função para copiar qualquer linha para uma área de transferência com o brinde mostrando:
fun String.toClipboard(toast: Int) { val clip = ClipData.newPlainText(this, this) (App.APP.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?)?.setPrimaryClip(clip) Toast(toast) }
RecyclerView e TableLayout
E no final deste pequeno artigo, quero compartilhar o que recentemente tive que resolver. Talvez alguém venha a calhar.
Os dados iniciais são os seguintes:
- 1k + dados a serem exibidos.
- Cada "célula" consiste em aproximadamente 10 campos.
- é necessário capturar o furto, clicar, clicar duas vezes, clicar longa.
- e ... os dados são atualizados a cada 300 a 500 milissegundos.
Se você esquecer o primeiro ponto. a solução mais funcional é TableLayout. Por que não o RecyclerView? E por causa de 3 e 4 pontos. Há otimizações dentro da planilha e reutiliza a visualização, mas nem sempre. E no momento da criação de uma nova exibição, os manipuladores de toque não existem. Tudo bem, se isso afetasse apenas o furto, mas de tempos em tempos o problema é reproduzido com o toque usual. Mesmo atualizando os dados diretamente no modo de exibição não ajuda, e não através de notificação. Portanto, foi decidido usar TableLayout. E tudo estava bem, até que os dados não ultrapassassem 100. E então - bem-vindo ao mundo dos congelamentos.
Vi duas maneiras de resolver - ou ensinar o TableLayout a reutilizar células e fazer mágica ao rolar. Ou tente fazer amigos RecyclerView e atualizações frequentes. E eu fui pelo segundo caminho. Como os toques e furtos (principalmente devido aos furtos) foram processados por uma classe auto-escrita com base no View.OnTouchListener, tornou-se uma solução eficaz para mover o processamento do toque para o nível RecyclerView, substituindo o método dispatchTouchEvent.
O algoritmo é simples:
- pegar toque
- determinar qual filho o toque está voando com findChildViewUnder (x, y)
- obter a posição do elemento no LayoutManager
- se for MotionEvent.ACTION_MOVE, verificaremos com a mesma posição em que trabalhamos ou não
- executar a lógica incorporada para tocar
Talvez no futuro ainda haja problemas com esse método, mas no momento tudo está funcionando e isso é bom.