Android项目的自制“糖”或“如何不做”

本文是一组用于Android项目的小方糖,我在适当的时候来了,并且派上了用场。 有些事情可能不是理想的解决方案,但它们可能对您很方便,就像它们一次对我有用。

应用和吐司


始终可以派上用场并且有时在程序中的任何时候都需要的第一件事是到应用程序的链接。 这可以通过一个简单的类来解决,该类的链接已在AndroidManifest中注册。

class App : Application() { init { APP = this } companion object { lateinit var APP: App } } 

因此,始终可以访问整个应用程序的上下文,并且您可以从任何地方获取线路/资源。 至少对于以下糖类而言这是必需的:

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

这很简单,但是要感谢Kotlin和我们可以访问上下文的事实,现在您可以在应用程序中的任何位置简短,简洁地调用Toast。 为了使该方法在任何地方都可以访问,可以在文件中将其标记出来,而无需指定根类。

屏幕上是谁?


具有一项活动的程序越来越受欢迎。 但是对于我们的体系结构,决定使用多个Activity。 至少要分开授权逻辑和应用程序的主要部分。 随着时间的流逝,有必要了解屏幕是否可见以及它在应用程序的哪一部分。 将来,还需要在应用程序语言环境中获取字符串。 但是首先是第一件事。 为了使我们的APP价值不会变得无聊,公司将使他:

 var screenActivity: AppCompatActivity? = null var onScreen: Boolean = false 

然后,我们创建继承自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 } } 

是的,对于新指南,您可以在必要的地方订阅带有活动的生命周期。 但是我们拥有我们所拥有的。

本地化字符串


有一些功能值得怀疑,但是传统知识就是传统知识。 对于这种功能,我将在应用程序而不是系统语言中选择语言。 长期以来,有一个代码允许您以编程方式替换该语言。 但是我们遇到了一个错误,这个错误可能仅在我们身上重复出现。 该错误的本质是,如果您通过应用程序上下文(而不是活动上下文)进行一行访问,则该行将返回到系统区域设置。 并且抛出一个Activity的上下文并不总是很方便。 可以采用以下方法进行救援:

 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) 

现在,您可以从应用程序中的任何位置以正确的语言环境获取字符串。

共享首选项


与所有应用程序一样,我们的应用程序必须在SharedPreferences中存储一些设置。 为了简化生活,发明了一个类,它本身隐藏了一些逻辑。 首先,为APP变量添加了一个新朋友:

 lateinit var settings: SharedPreferences 

它会在应用程序启动时初始化,并且对我们始终可用。

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

当然,您将必须为每种类型的变量生成一个此类,但是您可以使用所有必要的设置替换单例,例如:

 val PREF_LANGUAGE = PreferenceString("pref_language", "ru") 

现在,您始终可以将设置称为字段,并且通过侦听器进行的加载/保存和通信将被隐藏。

方向和平板电脑


您必须同时支持这两种情况吗? 您如何确定自己现在所处的方向? 我们有一个方便的方法,可以在任何地方调用它,而不必关心上下文:

 fun isLandscape(): Boolean { return getRes().configuration.orientation == Configuration.ORIENTATION_LANDSCAPE } 

如果输入值/ dimen.xml:

 <bool name="isTablet">false</bool> 

并在values-large / dimen.xml中:

 <bool name="isTablet">true</bool> 

然后,您还可以创建一个方法:

 fun isTablet(): Boolean { return getRes().getBoolean(R.bool.isTablet) } 

日期格式


多线程。 有时候这是一个令人恐惧的词。 一旦我们在后台形成日期行时发现了一个非常奇怪的错误。 事实证明,SimpleDateFormat不是线程安全的。 因此,诞生了以下内容:

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

还有一个用法示例(是的,再次在单例内部使用):

 private val utcDateSendSafeFormat = ThreadSafeDateFormat("yyyy-MM-dd", true) val utcDateSendFormat: SimpleDateFormat get() = utcDateSendSafeFormat.formatter 

对于整个应用程序,什么都没有改变,并且线程问题已经解决。

文字观察者


而且,您从来没有打扰过,如果需要捕获在EditText中输入的文本,则需要使用TextWatcher并实现3(!)方法。 不是很关键,但是不方便。 一切都由班级决定:

 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) {} } 

琴键


总是需要什么。 您需要立即显示键盘,然后在某个时候将其隐藏。 然后需要以下两种方法。 在第二种情况下,您需要传递根视图。

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

而且,也许有人会派上用场,该功能可以将任何行复制到剪贴板并显示吐司:

 fun String.toClipboard(toast: Int) { val clip = ClipData.newPlainText(this, this) (App.APP.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?)?.setPrimaryClip(clip) Toast(toast) } 

RecyclerView和TableLayout


在这篇小文章的结尾,我想分享一下我最近必须解决的问题。 也许有人会派上用场。

初始数据如下:

  1. 要显示1k +数据。
  2. 每个“单元”由大约10个字段组成。
  3. 有必要捕捉滑动,点击,doubleClick,longClick。
  4. 还有... 数据每300到500毫秒更新一次。

如果您忘记了第一点。 那么最有效的解决方案是TableLayout。 为什么不使用RecyclerView? 并且由于3和4分。 工作表内部有一些优化,它可以重用视图,但并非总是如此。 并且在创建新视图时,触摸处理程序不存在。 好的,如果它只影响滑动,但有时会用通常的水龙头重现该问题。 即使直接在View中更新数据也无济于事,也无法通过notify进行。 因此,决定使用TableLayout。 一切都很好,直到数据不超过100。然后-欢迎来到冻结的世界。

我看到了两种解决方法-或教导TableLayout重用单元格并在滚动时做魔术。 或尝试结交RecyclerView和经常更新的朋友。 我走了第二条路。 由于触摸和滑动(主要是由于滑动)是由基于View.OnTouchListener的自写类处理的,因此事实证明这是通过重写dispatchTouchEvent方法将触摸处理移至RecyclerView级别的有效解决方案。

该算法很简单:

  • 接触
  • 使用findChildViewUnder(x,y)确定触摸正在飞向哪个孩子
  • 从LayoutManager获取元素的位置
  • 如果是MotionEvent.ACTION_MOVE,那么我们检查与以前工作位置是否相同
  • 执行嵌入式逻辑以进行触摸

也许将来这种方法仍然会有问题,但是目前一切正常,这很好。

Source: https://habr.com/ru/post/zh-CN472196/


All Articles