"Açúcar" caseiro para um projeto Android ou "Como não fazer"

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:

  1. 1k + dados a serem exibidos.
  2. Cada "célula" consiste em aproximadamente 10 campos.
  3. é necessário capturar o furto, clicar, clicar duas vezes, clicar longa.
  4. 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.

Source: https://habr.com/ru/post/pt472196/


All Articles