در اپلیکیشن های تلوزیون اندروید، نمایش دادن دیالوگ های معمولی اندرویدی خیلی مرسوم نیست. برای اینکه امکان انتخاب بین چند گزینه یا پرسیدن سوال از کاربر رو داخل اپلیکیشن قرار بدهیم، می توانیم از فرگمنتی که خود کتابخانه LeanBack در اختیارمون قرار داده استفاده کنیم. به اسم GuidedStepFragment

ابتدا یک اکتیویتی بسازید به نام FragmentActivity. این اکتیویتی نیازی به layout ندارد، تنها باید از کلاس AppCompatActivity ارث ببرد.

 بعد داخل MovieDetailsFragment این اکتیویتی رو با کلیک روی Action Watch این اکتیویتی رو باز می کنیم. برای اینکار داخل متد setMovieDetailActions در MovieDetailsFragment که دکمه های Action رو ساختیم، یک setOnItemViewClickedListener به آداپتر اکشن ها اضافه می کنیم:

private fun setMovieDetailActions() {
        val actionsAdapter = ArrayObjectAdapter().apply {
            add(Action(1, "Watch").apply {
                icon =
                    ContextCompat.getDrawable(requireContext(), R.drawable.ic_play_arrow_white_24dp)
            })
            add(Action(2, "78 People", "Liked the Movie").apply {
                icon =
                    ContextCompat.getDrawable(requireContext(), R.drawable.ic_thumb_up_white_24dp)
            })
            add(Action(3, "Watch Trailer"))

            setOnItemViewClickedListener { itemViewHolder, item, rowViewHolder, row ->
                if (item is Action){
                    if (item.id == 1L){
                        startActivity(Intent(requireContext(), GuidedStepActivity::class.java))
                    }
                }
            }
        }
        detailOverviewRow.actionsAdapter = actionsAdapter
    }

همینطور کلاس زیر رو ایجاد میکنم (در اصل فرگمنتی که قرار هست دیالوگ را نشان بدهد):

class ChooseStartPositionFragment: GuidedStepSupportFragment() {}

و بعد داخل اکتیویتی GuidedStepActivity  ، این فرگمنت را نمایش می دهم.

class GuidedStepActivity : FragmentActivity () {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (null == savedInstanceState) {
            GuidedStepSupportFragment.add(supportFragmentManager, ChooseStartPositionFragment())
        }
    }
}

ساختار GuidedStepFragment :

ساختار GuidedStepFragment

این فرگمنت در اصل ساختاری به شکل بالا دارد. که با Override کردن چند متد می توانیم آن را ایجاد کنیم:

onCreateGuidance(Bundle): برای ایجاد View سمت چپ (guidance view) و مقداردهی به بخش title، description و icon

onCreateActions(List, Bundle): برای ایجاد قسمت گزینه های انتخابی کاربر که view سمت راست را ایجاد می کند. (َActions view)

onGuidedActionClicked(GuidedAction): که برای مقدار دهی به کلیک شدن آیتم های Actions View صدا زده میشود.

پیاده سازی این فرگمنت خیلی ساده است و در زیر کدش رو میبینیم:

class ChooseStartPositionFragment: GuidedStepSupportFragment() {
    override fun onCreateGuidance(savedInstanceState: Bundle?): GuidanceStylist.Guidance {
        return GuidanceStylist.Guidance("Don you wanna continue watching?", "You have watched the movie before, start from beginning or continue?",
            null, ContextCompat.getDrawable(requireContext(), R.drawable.ghost))
    }

    override fun onCreateActions(actions: MutableList<GuidedAction>, savedInstanceState: Bundle?) {
        actions.add(0, GuidedAction.Builder(requireContext()).id(0).icon(R.drawable.ic_watch_before).title("Continue watching").build())
        actions.add(1, GuidedAction.Builder(requireContext()).id(1).icon(R.drawable.ic_play_arrow_black_24dp).title("Start from beginning").build())
    }

    override fun onGuidedActionClicked(action: GuidedAction?) {
        when(action?.id){
             0L -> Toast.makeText(requireContext(), "You will continue watching", Toast.LENGTH_LONG).show()
             1L -> Toast.makeText(requireContext(), "You will watch from beginning", Toast.LENGTH_LONG).show()
        }
    }
}

خروجی:

یا حتی با دادن یک checkSetId واحد به Action هایی که می خواهیم حالت radioButton داشته باشند، خروجی زیر را خواهیم داشت:

سفارشی سازی ظاهر GuidedStepFragment:

بطور کل برای Custom کردن صفحات دیفالت LeanBack دو راه داریم.

راه اول تغییر style مرتبط با بخش مورد نظرمون هست. برای GudedStep ها، تم زیر در LeanBack تعریف شده: Theme.Leanback.GuidedStep

برای دیدن item هایی که این theme دار می توانیم به این لینک(https://android.googlesource.com/platform/frameworks/support/+/e220922/v17/leanback/res/values/themes.xml) و یا کد خود این کتابخانه مراجعه کنید.

با اضافه کردن یک تم به styles.xml پروژه که از Theme.Leanback.GuidedStep ارث برده باشد، و با تغییر item هایی که میخواهیم، می توانیم این صفحه را سفارشی کنیم. بطور مثال style زیر را در styles.xml تعریف می کنم:

<style name="Theme.Custom.Leanback.GuidedStep" parent="Theme.Leanback.GuidedStep">
    <item name="guidedStepBackground">#0C394E</item>
</style>

و بعد آن را در manifest به اکتیویتی مربوطه می دهم:

<activity android:name=".views.activities.GuidedStepActivity" 
    android:theme="@style/Theme.Custom.Leanback.GuidedStep"></activity>

که guidedStepBackground  دقیقا نامی هست که در LeanBack بعنوان رنگ پس زمینه GuidaceView اعمال می شود. و خروجی این تغییر:

راه دوم ویرایش layout ای است که برای هر قسمتی از LeanBack که میخواهیم سفارشی کنیم استفاده شده. البته قطعا ما توانایی تغییر کد اصلی LeanBack رو نداریم، فقط دقیقا یک layout با نام مشابهی که در leanback استفاده شده ایجاد می کنیم، و با رعایت نگهداری id هایی که به view ها داده، آن ها را ویرایش می کنیم. البته می توانیم view اضافه کنیم، ولی در صورت حذف یک view احتمال زیاد با خطا یا crash مواجه می شیم!

بطور مثال برای GuidanceStepFragment در لین بک، layout زیر ساخته شده(lb_guidedstep_fragment.xml):

<?xml version="1.0" encoding="utf-8"?>
<androidx.leanback.app.GuidedStepRootLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/guidedstep_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="bottom"
    android:weightSum="2">

    <FrameLayout
        android:id="@+id/guidedstep_background_view_root"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="?attr/guidedStepHeightWeight">

        <LinearLayout
            android:id="@+id/content_frame"
            android:orientation="horizontal"
            android:baselineAligned="false"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.leanback.widget.NonOverlappingFrameLayout
                android:id="@+id/content_fragment"
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="match_parent" />

            <androidx.leanback.widget.NonOverlappingFrameLayout
                android:id="@+id/action_fragment_root"
                android:transitionName="action_fragment_root"
                android:transitionGroup="false"
                android:orientation="horizontal"
                android:clipToPadding="false"
                android:clipChildren="false"
                android:paddingStart="@dimen/lb_guidedactions_section_shadow_width"
                android:layout_width="0dp"
                android:layout_weight="?attr/guidedActionContentWidthWeight"
                android:layout_height="match_parent" >

                <androidx.leanback.widget.NonOverlappingView
                    android:id="@+id/action_fragment_background"
                    android:transitionName="action_fragment_background"
                    android:orientation="horizontal"
                    android:outlineProvider="paddedBounds"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="?attr/guidedActionsBackground"
                    android:elevation="?attr/guidedActionsElevation" />

                <androidx.leanback.widget.NonOverlappingLinearLayout
                    android:id="@+id/action_fragment"
                    android:focusable="true"
                    android:descendantFocusability="afterDescendants"
                    android:transitionName="action_fragment"
                    android:transitionGroup="false"
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:elevation="?attr/guidedActionsElevation" />
            </androidx.leanback.widget.NonOverlappingFrameLayout>

        </LinearLayout>

    </FrameLayout>

</androidx.leanback.app.GuidedStepRootLayout>

من با ساختن layout دقیقا با همین نام، خیلی راحت آن را بازنویسی می کنم. در اینجا هدف من تغییر رنگ پس زمینه Actions View هست، پس تگ زیر رو تغییر می دهم:

<androidx.leanback.widget.NonOverlappingLinearLayout
    android:id="@+id/action_fragment"
    android:focusable="true"
    android:descendantFocusability="afterDescendants"
    android:transitionName="action_fragment"
    android:transitionGroup="false"
    android:background="#3FA9B8"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:elevation="?attr/guidedActionsElevation" />

و خروجی:

طبق قول کدها قرار هست داخل گیت قرار بگیرند. پس من داخل ریپوزیتوری گیتهابی که به این پروژه تخصیص دادم، روی برنچ iodroid_tv/add_guidance_fragmentکد این بخش از آموزش رو قرار دادم. (طبیعتا کد نهایی هم روی برنج master موجود هست)
برای دسترسی به این شاخه از پروژه گیتهاب کلیک کنید 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *
You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>