
منذ حوالي عام ، بدأت في استخدام Kotlin في مشاريع Android. كنت أرغب في تجربة شيء جديد سيكون من المثير للاهتمام أن أتعلمه. ثم صادفت أنكو . بحلول ذلك الوقت ، كانت كتابة واجهة المستخدم باللغة xml باهتة للغاية. لطالما أحببت كتابة الواجهة بيدي ، دون اللجوء إلى WYSIWYG وترميز xml المستخدم في Android Studio. السلبية الوحيدة هي أنه سيكون عليك إعادة تشغيل التطبيق للتحقق من أي تغييرات. يمكنك استخدام مكون إضافي يوضح شكل واجهة المستخدم دون بدء التطبيق ، لكن بدا لي غريباً إلى حد ما. لديه أيضًا قدرة رائعة على تحويل xml إلى Anko Layouts DSL.
أكبر عيب في المكتبة هو النقص شبه الكامل في الوثائق. لمعرفة كيفية استخدامها بشكل صحيح ، غالبًا ما كان علي النظر إلى المصدر. في هذه المقالة ، ستتم مناقشة تطوير التطبيق باستخدام Anko Layouts و Anko Coroutines بالتفصيل.
مكتبة Anko نفسها مقسمة إلى 4 أجزاء مستقلة:
- تخطيطات Anko - بناء واجهة مستخدم.
- Anko Commons - أدوات وميزات مفيدة.
- Anko SQLite - العمل مع قاعدة بيانات SQLite.
- Anko Coroutines هي أدوات مفيدة للعمل مع coroutines.
لإضافة مكتبة إلى المشروع ، ما عليك سوى إضافة سطر واحد حسب المشروع:
implementation "org.jetbrains.anko:anko:$anko_version"
حيث anko_version
هو الإصدار الحالي للمكتبة ، المسجل في ملف build.gradle على مستوى المشروع:
ext.anko_version='0.10.8'
أنكو تخطيطات
يسمح لك Anko Layouts بتطوير تطبيقات Android UI بشكل أكثر كفاءة مما كان يستخدم Java.
اللاعب الرئيسي في الحقل معنا هو واجهة AnkoComponent<T>
بأسلوب createView واحد يقبل AnkoContext<T>
ويعيد طريقة عرض. في هذه الطريقة يتم إنشاء واجهة المستخدم بأكملها. واجهة AnkoContext<T>
عبارة عن غلاف على ViewManager . المزيد عن ذلك سيكون في وقت لاحق.
بعد قليل من فهم كيفية عمل AnkoComponent<T>
، دعونا نحاول إنشاء واجهة مستخدم بسيطة في فئة نشاطنا. تجدر الإشارة إلى أن مثل هذا الإملاء "المباشر" الخاص بـ UI ممكن فقط في النشاط ، حيث يتم كتابة ankoView بوظيفة امتداد منفصلة لها ، والتي تسمى طريقة addView ، ويتم AnkoContextImpl<T>
في الطريقة نفسها باستخدام المعلمة setContentView = true
.
class AppActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) verticalLayout { lparams(matchParent, matchParent) gravity = Gravity.CENTER textView("Cool Anko TextView") { gravity = Gravity.CENTER } } }
من الواضح أنه بالنسبة لأكثر من TextView واحد ، فإن طريقة onCreate ستتحول بسرعة إلى تفريغ. دعنا نحاول فصل فئة النشاط عن واجهة المستخدم. للقيام بذلك ، قم بإنشاء فصل آخر سيتم وصفه به.
class AppActivityUi: AnkoComponent<AppActivity> { override fun createView(ui: AnkoContext<AppActivity>): View = with(ui) { verticalLayout { lparams(matchParent, matchParent) gravity = Gravity.CENTER textView("Cool Anko TextView") { gravity = Gravity.CENTER } } } }
الآن ، من أجل تمرير واجهة المستخدم الخاصة بنا إلى نشاطنا ، يمكنك استخدام
AppActivityUi().setContentView(this)
حسنًا ، لكن ماذا لو أردنا إنشاء واجهة مستخدم للجزء؟ للقيام بذلك ، يمكنك استخدام طريقة createView مباشرة عن طريق الاتصال بها من طريقة جزء onCreateView. يبدو مثل هذا:
class AppFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return AppFragmentUi().createView(AnkoContext.create(requireContext(), this)) } }
كما سبق ذكره - AnkoContext<T>
عبارة عن غلاف على ViewManager. يحتوي كائن المرافق الخاص به على ثلاث طرق رئيسية تقوم بإرجاع AnkoContext<T>
. سنقوم بتحليلها بمزيد من التفاصيل.
خلق
fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T>
وشقيقه التوأم
fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context>
إرجاع AnkoContextImpl<T>
.
تستخدم الطرق في جميع الحالات القياسية ، على سبيل المثال ، في الأمثلة السابقة مع النشاط والجزء. الأكثر إثارة للاهتمام هنا هي خيارات المالك و setContentView. الأول يسمح لك بتمرير جزء محدد أو نشاط أو أي شيء آخر إلى طريقة مثيل createView.
MyComponent().createView(AnkoContext.create(context, myVew)) class MyComponent: AnkoComponent<View> { override fun createView(ui: AnkoContext<View>): View = with(ui) { val myView: View= ui.owner
ستحاول المعلمة الثانية - setContentView - إضافة طريقة العرض الناتجة تلقائيًا إذا كان Context عبارة عن نشاط أو ContextWrapper . إذا لم ينجح ، فسيرمي IllegalStateException.
createDelegate
قد تكون هذه الطريقة مفيدة للغاية ، لكن الوثائق الرسمية على gihab لا تذكر أي شيء عنها. صادفتها في المصدر عندما حللت مشكلة انتفاخ فئات واجهة المستخدم:
fun <T: ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner)
يسمح لك بإضافة نتيجة مكون createView إلى المالك.
النظر في استخدامه كمثال. افترض أن لدينا فئة كبيرة تصف إحدى شاشات التطبيق - AppFragmentUi.
verticalLayout { relativeLayout { id = R.id.toolbar
منطقيا ، يمكن تقسيمها إلى جزأين - شريط الأدوات والمحتوى ، AppFragmentUiToolbar و AppFragmentUiContent ، على التوالي. ثم سيصبح AppFragmentUi الخاص بالفئة الرئيسية أكثر بساطة:
class AppFragmentUi: AnkoComponent<AppFragment> { override fun createView(ui: AnkoContext<AppFragment>) = with(ui) { verticalLayout { AppFragmentUiToolbar().createView(AnkoContext.createDelegate(this)) AppFragmentUiContent().createView(AnkoContext.createDelegate(this)) } } } class AppFragmentUiToolbar : AnkoComponent<_LinearLayout> { override fun createView(ui: AnkoContext<_LinearLayout>): View = with(ui.owner) { relativeLayout { id = R.id.toolbar
لاحظ أنه ليس واجهة المستخدم ، ولكن يتم تمرير ui.owner إلى الدالة with
ككائن.
وبالتالي ، لدينا الخوارزمية التالية:
- يتم إنشاء مثيل المكون.
- تقوم طريقة createView بإنشاء طريقة العرض المراد إضافتها.
- تتم إضافة طريقة العرض الناتجة إلى المالك.
أقرب تقريب: this.addView(AppFragmentUiToolbar().createView(...))
كما ترون ، فإن الخيار مع createDelegate أكثر قابلية للقراءة.
createReusable
يبدو أن AnkoContext.create قياسي ، ولكن مع إضافة صغيرة - تعتبر طريقة العرض الأخيرة طريقة عرض الجذر:
class MyComponent: AnkoComponent<MyObject> { override fun createView(ui: AnkoContext<MyObject>): View = with(ui) { textView("Some text")
في التطبيق القياسي ، إذا تم تعيين طريقة عرض الجذر ، فإن محاولة تعيين طريقة العرض الثانية في نفس الوقت ستؤدي إلى استثناء .
الأسلوب createReusable يُرجع فئة ReusableAnkoContext ، التي ترث من AnkoContextImpl وتتجاوز طريقة alreadyHasView()
.
Customview
لحسن الحظ ، لا تقتصر Anko Layouts على هذه الوظيفة. إذا احتجنا إلى عرض CustomView الخاص بنا ، فلا يتعين علينا الكتابة
verticalLayout { val view = CustomView(context)
للقيام بذلك ، يمكنك إضافة المجمع الخاص بك ، والتي سوف تفعل الشيء نفسه.
المكونات الرئيسية هنا هي طريقة التمديد <T: View>ankoView(factory: (Context) -> T, theme: Int, init: T.() -> Unit)
من ViewManager أو Context أو Activity.
- المصنع - وظيفة ، يتم إدخال السياق الذي يتم إرجاعه ويتم عرض المشاهدة. في الواقع ، إنه المصنع الذي يتم فيه إنشاء العرض.
- السمة هي مورد نمط سيتم تطبيقه على العرض الحالي.
- الحرف الأول هو وظيفة يتم فيها تعيين المعلمات الضرورية لطريقة العرض التي تم إنشاؤها.
إضافة التنفيذ الخاص بك لدينا CustomView
inline fun ViewManager.customView(theme: Int = 0, init: (CustomView).() -> Unit): CustomView { return ankoView({ CustomView(it) }, theme, init) }
الآن يتم إنشاء CustomView لدينا بكل بساطة:
customView { id = R.id.customview
يمكنك استخدام lparams لتطبيق LayoutParams على طريقة العرض.
textView("text") { textSize = 12f }.lparams(width = matchParent, height = wrapContent) { centerInParent() }
تجدر الإشارة إلى أن هذا لا ينطبق على جميع طريقة العرض - يتم إعلان جميع طرق lparams عادةً في غلافات. على سبيل المثال ، _RelativeLayout عبارة عن مجمّع عبر RelativeLayout . وكذلك للجميع.
لحسن الحظ ، تمت كتابة عدة غلافات لمكتبة دعم Android ، بحيث يمكنك فقط تضمين التبعيات في ملف gradle.
// Appcompat-v7 (Anko Layouts) implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version" implementation "org.jetbrains.anko:anko-coroutines:$anko_version" // CardView-v7 implementation "org.jetbrains.anko:anko-cardview-v7:$anko_version" // Design implementation "org.jetbrains.anko:anko-design:$anko_version" implementation "org.jetbrains.anko:anko-design-coroutines:$anko_version" // GridLayout-v7 implementation "org.jetbrains.anko:anko-gridlayout-v7:$anko_version" // Percent implementation "org.jetbrains.anko:anko-percent:$anko_version" // RecyclerView-v7 implementation "org.jetbrains.anko:anko-recyclerview-v7:$anko_version" implementation "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version" // Support-v4 (Anko Layouts) implementation "org.jetbrains.anko:anko-support-v4:$anko_version" // ConstraintLayout implementation "org.jetbrains.anko:anko-constraint-layout:$anko_version"
من بين أشياء أخرى ، تسمح المكتبة بتنفيذ أكثر راحة لمختلف المستمعين. مثال صغير من المستودع:
seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar) = Unit })
والآن باستخدام Anko
seekBar { onSeekBarChangeListener { onProgressChanged { seekBar, progress, fromUser -> // do something } } }
أيضا ، بعض المستمعين دعم coroutines:
verticalLayout{ val anyCoroutineContext = GlobalScope.coroutineContext onClick(anyCoroutineContext) {
أنكو coroutines
لنقل كائنات حساسة لتسرب الذاكرة بأمان ، يتم asReference
طريقة asReference
. يعتمد على WeakReference وإرجاع كائن Ref.
verticalLayout{ val activity = ui.owner val activityReference: Ref<AppActivity> = activity.asReference() onClick(anyCoroutineContext) { ref().doSomething() } }
افترض أنك تريد إضافة دعم لـ corutin في ViewPager.OnPageChangeListener القياسي. دعنا نجعلها باردة مثل مثال askbar.
أولاً ، قم بإنشاء فصل منفصل ورث من ViewPager.OnPageChangeListener .
class CoroutineOnPageChangeListener( private val coroutineContext: CoroutineContext = Dispatchers.Main ) : ViewPager.OnPageChangeListener { }
سنقوم بتخزين lambdas في المتغيرات التي سيتم استدعاء ViewPager.OnPageChangeListener .
private var onPageScrollStateChanged: ((Int, CoroutineContext) -> Unit)? = null private var onPageScrolled: ((Int, Float, Int, CoroutineContext) -> Unit)? = null private var onPageSelected: ((Int, CoroutineContext) -> Unit)? = null
ننفذ التهيئة لأحد هذه المتغيرات (تتم الباقي بنفس الطريقة)
fun onPageScrollStateChanged(action: ((Int, CoroutineContext) -> Unit)?) { onPageScrollStateChanged = action }
وفي النهاية ننفذ وظيفة بنفس الاسم.
override fun onPageScrollStateChanged(state: Int) { GlobalScope.launch(coroutineContext) { onPageScrollStateChanged?.invoke(state, coroutineContext) } }
يبقى لإضافة وظيفة تمديد لجعلها تعمل
fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.() -> Unit) { val listener = CoroutineOnPageChangerListener() listener.init() addOnPageChangeListener(listener) }
ولصق هذا الشيء كله تحت ViewPager
viewPager { onPageChangeListenerCoroutines { onPageScrolled { position, offset, pixels, coroutineContext ->
كود كامل هنا class CoroutineOnPageChangeListener( private val coroutineContext: CoroutineContext = Dispatchers.Main ) : ViewPager.OnPageChangeListener { private var onPageScrollStateChanged: ((Int, CoroutineContext) -> Unit)? = null private var onPageScrolled: ((Int, Float, Int, CoroutineContext) -> Unit)? = null private var onPageSelected: ((Int, CoroutineContext) -> Unit)? = null fun onPageScrollStateChanged(action: ((Int, CoroutineContext) -> Unit)?) { onPageScrollStateChanged = action } fun onPageScrolled(action: ((Int, Float, Int, CoroutineContext) -> Unit)?) { onPageScrolled = action } fun onPageSelected(action: ((Int, CoroutineContext) -> Unit)?) { onPageSelected = action } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { GlobalScope.launch(coroutineContext) { onPageScrolled?.invoke(position, positionOffset, positionOffsetPixels, coroutineContext) } } override fun onPageSelected(position: Int) { GlobalScope.launch(coroutineContext) { onPageSelected?.invoke(position, coroutineContext) } } override fun onPageScrollStateChanged(state: Int) { GlobalScope.launch(coroutineContext) { onPageScrollStateChanged?.invoke(state, coroutineContext) } } } fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.() -> Unit) { val listener = CoroutineOnPageChangerListener() listener.init() addOnPageChangeListener(listener) }
هناك أيضًا العديد من الطرق المفيدة في مكتبة Anko Layouts ، مثل الترجمات إلى مقاييس متنوعة .