هذه المقالة عبارة عن مجموعة من مكعبات السكر الصغيرة لمشروع أندرويد ، والتي جئت إليها في الوقت المناسب والتي جاءت مفيدة. قد لا تكون بعض الأشياء حلولًا مثالية ، ولكنها قد تكون مفيدة لك بالطريقة نفسها التي كانت مفيدة لي في وقت واحد.
التطبيق والخبز المحمص
أول ما يمكن أن يكون مفيدًا دائمًا ويكون ضروريًا في بعض الأحيان في أي وقت في البرنامج هو رابط للتطبيق. يتم حل هذا بواسطة فصل بسيط ، يتم تسجيل الرابط في 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 قريبًا ودقيقًا من أي مكان في التطبيق. حتى يمكن الوصول إلى هذه الطريقة في كل مكان ، يمكن تمييزها في ملف دون تحديد فئة الجذر.
من هو على الشاشة؟
البرامج ذات النشاط الواحد تكتسب شعبية. لكن بالنسبة إلى هندستنا ، فقد تقرر استخدام العديد من الأنشطة. على الأقل لفصل منطق الترخيص والجزء الرئيسي من التطبيق. بمرور الوقت ، كانت هناك حاجة لفهم ما إذا كانت الشاشة مرئية وما هو جزء التطبيق. وفي المستقبل ، كانت هناك حاجة أيضًا للحصول على سلاسل في لغة التطبيق. لكن أول الأشياء أولا. حتى لا تشعر بالملل من قيمة التطبيق لدينا ، ستجعله الشركة:
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 } }
نعم ، للحصول على أدلة جديدة ، يمكنك الاشتراك في دورة حياة مع النشاط في الأماكن الضرورية. ولكن لدينا ما لدينا.
سلاسل مترجمة
هناك وظائف فائدة مشكوك فيها ، ولكن المعارف التقليدية هي المعارف التقليدية. لمثل هذه الوظيفة ، أود أن أرجع اختيار اللغة في التطبيق بدلاً من نظام واحد. لفترة طويلة هناك رمز يسمح لك باستبدال اللغة برمجياً. لكن واجهنا خطأ واحد ، والذي ربما يتكرر فقط معنا. جوهر الخطأ هو أنه إذا كنت تأخذ الخط من خلال سياق التطبيق ، وليس من خلال سياق النشاط ، فسيتم إرجاع الخط إلى لغات النظام. وليس من المناسب دائمًا رمي سياق النشاط. جاءت الطرق التالية للإنقاذ:
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
كما هو الحال مع جميع التطبيقات ، لدينا لتخزين بعض الإعدادات في 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>
وفي القيم الكبيرة / dimen.xml:
<bool name="isTablet">true</bool>
ثم يمكنك أيضًا إنشاء طريقة:
fun isTablet(): Boolean { return getRes().getBoolean(R.bool.isTablet) }
DateFormat
خاصية تعدد. في بعض الأحيان أنها كلمة مخيفة. بمجرد أن وقعنا في خطأ غريب للغاية عندما شكلنا خطوط من التواريخ في الخلفية. اتضح أن 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
بالنسبة للتطبيق بالكامل ، لم يتغير أي شيء ، وتم حل المشكلة مع مؤشرات الترابط.
TextWatcher
ولن تزعجك أبدًا أنه إذا كنت بحاجة إلى التقاط النص الذي تم إدخاله في 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) }
إعادة التدوير و TableLayout
وفي نهاية هذا المقال الصغير ، أريد أن أشارك ما حلته مؤخرًا. ربما شخص ما سوف يأتي في متناول اليدين.
البيانات الأولية هي كما يلي:
- 1K + البيانات لعرضها.
- تتكون كل "خلية" من حوالي 10 حقول.
- من الضروري التقاط التمرير السريع ، النقر ، doubleClick ، longClick.
- و ... يتم تحديث البيانات كل 300 إلى 500 ميلي ثانية.
إذا نسيت النقطة الأولى. ثم الحل الأكثر العمل هو TableLayout. لماذا لا RecyclerView؟ وبسبب 3 و 4 نقاط. هناك تحسينات داخل الورقة وهي تعيد استخدام طريقة العرض ، ولكن ليس دائمًا. وفي وقت إنشاء طريقة عرض جديدة ، لم تكن معالجات اللمس موجودة. حسنًا ، إذا كان يؤثر فقط على التمرير السريع ، ولكن من وقت لآخر تتكرر المشكلة بنقرة عادية. حتى تحديث البيانات مباشرة في طريقة العرض لا يساعد ، وليس من خلال الإخطار. لذلك ، تقرر استخدام TableLayout. وكان كل شيء على ما يرام ، حتى البيانات لم يكن أكثر من 100. وبعد ذلك - مرحبا بكم في عالم يتجمد.
لقد رأيت طريقتين لحلها - أو لتعليم TableLayout لإعادة استخدام الخلايا والقيام بالسحر عند التمرير. أو حاول تكوين صداقات RecyclerView والتحديثات المتكررة. وذهبت في الطريق الثاني. نظرًا لأن اللمسات والضربات الشديدة (التي يرجع سببها بشكل رئيسي إلى الضربات الشديدة) تمت معالجتها بواسطة فصل مكتوب ذاتيًا استنادًا إلى View.OnTouchListener ، فقد أصبح حلاً فعالًا لنقل معالجة اللمس إلى مستوى RecyclerView عن طريق تجاوز طريقة الإرسال إلى dispatchTouchEvent.
الخوارزمية بسيطة:
- قبض على اللمس
- تحديد الطفل الذي تطير إليه اللمس مع findChildViewUnder (x، y)
- الحصول على موضع العنصر من LayoutManager
- إذا كان MotionEvent.ACTION_MOVE ، فإننا نتحقق من نفس الموقف الذي نعمل به من قبل أم لا
- أداء المنطق المضمن للمس
ربما في المستقبل ستظل هناك مشاكل من هذه الطريقة ، ولكن في الوقت الحالي كل شيء يعمل وهذا جيد.