کانال بله, جهت پشتیبانی و اطلاع رسانی کانال بله, جهت پشتیبانی و اطلاع رسانی
عضویت

برنامه نویسی Multi-Pane در اندروید

این مبحث برای شما شرح می دهد چگونه می توان با استفاده از کلاس fragment، اپلیکیشن هایی با رابط کاربری مقیاس و انعطاف پذیر ایجاد نمایید.

آموزش Fragment ها

آموزش شرح Layoutهای تک قطعه (single-pane) و چند قطعه (multi-pane)

همان طور که می دانید دستگاه های اندروید دارای نمایشگرهایی با اندازه و تراکم پیکسلی متفاوت هستند.
Panel یا pane در اندروید، عبارت است از یک بخش یا قطعه از کل صفحه (UI) که کاربر با آن تعامل دارد. pane در واقع یک واژه ی کلی است که بیانگر قابلیت اندروید برای پشتیبانی از چندین view در کنار هم و در قالب یک view مرکب (واحد) بوده که ممکن است بسته به اندازه ی فضای موجود در نمایشگر از دستگاه به دستگاهی دیگر متفاوت باشد.

شرح  Layoutهای تک قطعه (single-pane) و چند قطعه (multi-pane)

در صورت عدم وجود فضای کافی، تنها یک panel به نمایش در می آید. از این معمولا تحت عنوان چیدمان یا قالب (layout) تک قطعه نام برده می شود.

شرح  Layoutهای تک قطعه (single-pane) و چند قطعه (multi-pane)

اما در دستگاه هایی که صفحه ی نمایش عریض تری دارند و در نتیجه فضای بیشتری در دسترس است (همچون تبلت)، طراحی چند قطعه این امکان را برای برنامه فراهم می کند تا چندین panel را همزمان به نمایش بگذارد.

شرح  Layoutهای تک قطعه (single-pane) و چند قطعه (multi-pane)

آموزش شرح مفهوم Fragment

Fragment در حقیقت یک کامپوننت UI مستقل در اندروید است که زیرمجموعه ی یک activity می باشد و بخشی از کل صفحه یا فرم را تشکیل می دهد (در نمایشگرهای بزرگ چند fragment و در نمایشگرهای کوچک تنها یک fragment در لحظه نمایش داده شده و صفحه ی قابل مشاهده برای کاربر را تشیکل می دهند). به عبارت دیگر fragment در اندروید به شما اجازه می دهد تا UI انعطاف پذیر و سازگار با انواع نمایشگرها (کوچک و بزرگ) برای برنامه ی خود تعریف نمایید و اجزا رابط کاری اپلیکیشن را در قالب ماژول های مجزا کپسوله سازی نمایید.
Fragment با کپسوله سازی اجزا رابط کاربری و رفتار activity ها در ماژول های مجزا، این امکان را برای اپلیکیشن فراهم می کند تا از این ماژول های مجزا در چندین activity استفاده کند.
Fragment در بستر یک activity اجرا می شود، با این وجود دارای چرخه ی حیات و رابط کاربری اختصاصی خود است. البته یک fragment می تواند فاقد ظاهر و UI باشد که در اصطلاح headless fragment خوانده می شود.

آموزش Fragment ها و دسترسی به Context

Fragment ها از کلاس Context مشتق (subclass) نمی شوند. بنابراین برای دسترسی به activity میزبان بایستی متد getActivity() را صدا بزنید.

آموزش مزایای استفاده از fragment

  1. fragment ها چرخه ی حیات و رفتار خود را دارند.
  2. می توان آن ها را در زمان اجرای اپلیکیشن و به صورت داینامیک کم و زیاد کرد.
  3. می توان چندین fragment را با یکدیگر ترکیب کرده و UI های چند قطعه/چند پنجره ای و انعطاف پذیر ایجاد کرد.
  4. این امکان نیز وجود دارد که یک fragment را در چندین activity بکار برد.

به کمک fragment می توانید برای دستگاه های کوچک UI یک قطعه ایجاد نموده و برای نمایشگرهای بزرگ رابط کاربری چند پنجره طراحی نمایید. همچنین می توانید با استفاده از fragment هر دو جهت نمایش (landscape= نمای افقی و portrait= عمودی) در گوشی ها را مدیریت نمایید.
یکی از موارد کاربرد fragment در طراحی UI را می توان در ساخت لیست مشاهده کرد. اگر بر روی یک آیتم از لیست کلیک کنید، در صفحه ی تبلت، جزئیات مربوطه در همان صفحه، برای مثال در سمت راست به نمایش در می آیند. اما همین لیست در نمایشگر موبایل، کاربر را به صفحه ی مجزا حاوی جزئیات مرتبط هدایت می کند.

مزایای استفاده از fragment

برای این مبحث اپلیکیشن شما باید دو fragment داشته باشد: main و detail. البته شما می توانید بسته به نیاز خود fragment های بیشتری تعریف نمایید. بعلاوه اپلیکیشن شما می بایست دو activity به شرح زیر داشته باشد: main activity و detailed activity. در نمایشگر تبلت main activity هر دو fragment را در layout خود به نمایش می گذارد. این در حالی است که main activity در صفحه نمایش موبایل، فقط main fragment را در لحظه به نمایش می گذارد.

مزایای استفاده از fragment

آموزش طراحی اپلیکیشنی با UI انعطاف پذیر و سازگار با نمایشگرهای مختلف به وسیله ی fragment ها

Fragment ها را می توان به صورت static تعریف کنید، بدین معنی که به وسیله ی تگ
در فایل layout می توانید مشخص کنید که activity از چندین fragment و قطعه تشکیل شود.
همان طور که گفته شد، شما می توانید fragment های یک activity را در زمان اجرا ویرایش کنید (کم یا زیاد کنید( که به آن تعریف به صورت پویا و dynamic definition گفته می شود.
برای اینکه بتوانید با توجه به فضای موجود در نمایشگر خود چند fragment را نمایش دهید، کافی است بر اساس یکی از روش های زیر اقدام نمایید:

  1. از یک activity استفاده کنید که در نمایشگرهای بزرگ (تبلت) و کوچک (گوشی همراه) دو fragment را برای کاربر در لحظه نمایش می دهد و در زمان لازم، fragment های قابل مشاهده در activity (صفحه ی جاری) را به هنگام اجرای برنامه تغییر دهید. برای این منظور توصیه می شود دو نمونه از کلاس FrameLayout به عنوان placholder (مکان نگهدار موقتی) در layout خود تعریف نموده، سپس fragment ها را در زمان اجرا به آن دو کلاس اضافه کنید.
  2. در دستگاه های کوچک همچون گوشی، هر fragment را در activity جدا بگنجانید. برای مثال، اگر UI تبلت از دو fragment در یک activity استفاده می کند و در آن واحد دو قطعه را به نمایش می گذارد، همین activity را برای نمایشگر گوشی استفاده می کنید اما این بار فایل layout مجزا ارائه می دهید که تنها یک fragment را شامل می شود. چنانچه detailed fragment از قبل در نمایشگر حاضر باشد، آنگاه main activity به fragment دستور بروز رسانی خود را می دهد. اگر detail fragment در دسترس نبود، در آن صورت main activity، detail activity را راه اندازی می کند.

اینکه کدام گزینه را انتخاب کنید کاملا به نیاز و مورد استفاده ی شما بستگی دارد. به طور معمول ویرایش fragment در زمان اجرا منعطف تر است، اما پیاده سازی آن کمی پیچیده تر می باشد.

آموزش تعریف و استفاده از fragment ها

آموزش تعریف fragment

به منظور تعریف یک fragment جدید می توانید از کلاس android.app.Fragment و یا یکی از کلاس های فرزند (subclass) آن ارث بری (extend) نمایید. کلاس های فرزندی که می توانید مورد استفاده قرار دهید عبارتند از: ListFragment، DialogFragment، PreferenceFragment یا WebViewFragment. کد زیر یک نمونه پیاده سازی را نمایش می دهد:

package com.example.android.rssreader;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailFragment extends Fragment {
    public static final String EXTRA_URL ="url";
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_rssitem_detail,
                container, false);
        return view;
    }
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null) {
            String link = bundle.getString("url");
            setText(link);
        }
    }
        public void setText(String url) {
                TextView view = (TextView) getView().findViewById(R.id.detailsText);
                view.setText(url);
        }
}  

آموزش تعامل اپلیکیشن با fragment ها

برای اینکه بتوان امکان استفاده ی مجدد از fragment ها را افزایش داد و به اصطلاح آن ها را بازیافت نمود، لازم است هر گونه تعامل بین این fragment ها منحصرا از طریق activity میزبان انجام شود. به بیان دیگر، fragment ها نباید مستقیما و بدون واسطه با یکدیگر ارتباط داشته باشند.
برای این منظور fragment می بایست یک interface را به صورت inner و داخل یک کلاس تعریف کند. fragment ایجاب می کند که activity میزبان، این interface را پیاده سازی کند. بدین وسیله شما اطمینان حاصل می کنید که fragment هیچ اطلاعی از activity میزبان (activity که از آن fragment استفاده می کند) خود ندارد. Fragment مورد نظر سپس به وسیله ی متد onAttach() خود بررسی می کند که آیا activity به درستی interface مورد نظر را پیاده سازی می کند یا خیر.
به عنوان مثال، فرض کنید fragment ای دارد که باید مقداری را به activity میزبان (parent) خود ارسال کند. این کار را می توان به صورت زیر پیاده سازی نمود.

package com.example.android.rssreader;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
public class MyListFragment extends Fragment {
        private OnItemSelectedListener listener;
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                        Bundle savedInstanceState) {
                View view = inflater.inflate(R.layout.fragment_rsslist_overview,
                                container, false);
                Button button = (Button) view.findViewById(R.id.button1);
                button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                                updateDetail("fake");
                        }
                });
                return view;
        }
        public interface OnItemSelectedListener {
                public void onRssItemSelected(String link);
        }
        @Override
        public void onAttach(Context context) {
                super.onAttach(context);
                if (context instanceof OnItemSelectedListener) {
                        listener = (OnItemSelectedListener) context;
                } else {
                        throw new ClassCastException(context.toString()
                                        + " must implemenet MyListFragment.OnItemSelectedListener");
                }
        }
        @Override
        public void onDetach() {
                super.onDetach();
                listener = null;
        }
        // may also be triggered from the Activity
        public void updateDetail(String uri) {
                // create a string just for testing
                String newTime = String.valueOf(System.currentTimeMillis());
                // inform the Activity about the change based
                // interface defintion
                listener.onRssItemSelected(newTime);
        }
}

آموزش ارسال پارامتر به fragment ها

Activity می تواند یک bundle را به عنوان پارامتر به fragment ارسال کند.

detailFragment = new DetailFragment();
// configure link
Bundle bundle = new Bundle();
bundle.putString("link", link);
detailFragment.setArguments(bundle);

fragment آبجکت bundle را در متد onActivityCreated خود دریافت می کند.

                    @Override
public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null) {
                setText(bundle.getString("link"));
        }
}

آموزش چرخه ی حیات (life cycle) fragment

در اندروید هر fragment چرخه ی حیات مختص به خود را دارد. با این وجود چرخه ی حیات آن همیشه به activity میزبان آن متصل است.

چرخه ی حیات (life cycle) fragment

زمانی که activity متوقف می شود، همگام با آن fragment های زیرمجموعه ی این activity نیز متوقف می شوند. اگر activity از حافظه کلا پاک شود (destroy)، fragment های آن نیز همزمان از بین می روند.

شرح کارکرد
متد
کاربرد متد حاضر این است که fragment را به activity می شناساند. زمانی که onAttach() صدا خورده می شود، activity و fragment هیچ یک ایجاد نشده اند. شما می توانید داخل این متد عملیاتی که می خواهید پیش از ایجاد fragment انجام شود، تعریف نمایید. نمونه ی activity به نمونه ی fragment متصل است. به هنگام فراخوانی این متد activity و fragment، هنوز راه اندازی نشده اند. معمولا برنامه نویس اشاره گری به activity داخل این
متد قرار می دهید که از fragment برای مقداردهی بیشتر استفاده می کند.
() onAttach
اندروید زمانی این متد را صدا می زند که در حال ساختن fragment باشد. برنامه نویس می تواند کامپوننت هایی از fragment را که می خواهد در زمان ادامه یافتن activity، بعد از توقف حفظ شود را مقداردهی اولیه نمایید.
در واقع در این مرحله، fragment ایجاد شده است. این متد بعد از فراخوانده شدن onCreate() مربوط به activity و قبل از متد onCreateView() مربوط به fragment صدا خورده می شود.
() onCreate
نمونه ی fragment، زنجیره یا سلسله مراتب view های خود را ایجاد می کند. به عبارت دیگر fragment در متد onCreateView()، رابط کاربری یا ظاهر خود را تعریف می کند. در این متد با فراخوانی متد inflate() از آبجکت Inflator که به عنوان پارامتر به این متد فرستاده می شود، برنامه نویس می تواند یک layout را بارگذاری کرده و نمایش (inflate) دهد.
در این متد نباید با activity تعامل داشته باشید. Activity هنوز کاملا ساخته و مقداردهی اولیه نشده است.
لزومی ندارد این متد را برای fragment های فاقد UI/headless فراخوانی کنید. view های inflate شده بخشی از زنجیره ی view های activity میزبان محسوب می شوند.
به عبارت دیگر، هنگامی که fragment می خواهد view های خود را بسازد این متد را صدا می زند. به منظور پیاده سازی UI یا رابط کاری، برنامه نویس می بایست یک آبجکت view که
پوشه ی حاوی fragment را مشخص می کند، به عنوان خروجی این متد تعریف کنید. اگر fragment رابط کاربری نداشته باشد، می توانید خروجی متد را null تعریف کنید.
() onCreateView
متد جاری پس از فراخوانی متد onCreateView() و زمانی که activity میزبان ایجاد شده است، صدا خورده می شود.
نمونه ی Activity و fragment همراه با زنجیره ی view های activity ساخته شده اند. در این برهه، می توان با فراخوانی متد findViewById() به view دلخواه دسترسی داشت.
در این متد شما می توانید از آبجکت هایی که به یک آبجکت Context نیاز دارند، نمونه سازی نمایید.
به عبارت دیگر، این متد بعد از onCreateView() و هنگامی که activity میزبان ایجاد شده، call می شود. activity و fragment به صورت یک آبجکت view سلسله مراتبی برای activity تولید می شوند.
() onActivityCreated
The onStart() method is called once the fragment gets visible.
متد onStart() زمانی صدا زده می شود که fragment نمایان و قابل مشاهده شود.
() onStart
زمانی که Fragment در حال اجرا است.
onResume()
Fragment قابل مشاهده است اما دیگر فعال نیست مانند زمانی که activity دیگری، با انیمیشن بر روی activity حامل fragment قرار می گیرد.
Fragment با کاربر تعاملی ندارد. حال یا activity آن در حال متوقف شدن است یا یک عملیات مربوط به fragment در حال ویرایش آن در activity است.
() onPause
Fragment دیگر برای کاربر قابل مشاهده نیست.
() onStop
View یا المان های رابط کاربری قابل مشاهد در fragment را پاک می کند. اگر fragment از backstack ساخته شده باشد، در آن صورت این متد فراخوانی می شود و پس از آن onCreateView صدا زده می شود. در واقع پس از فراخوانی این متد UI و ظاهر fragment به کلی از بین می رود.
() onDestroyView
این متد cleanup و پاک سازی نهایی fragment ها را انجام می دهد. لازم به ذکر است که این متد ممکن است اصلا فراخوانی نشود.
() onDestroy

آموزش تعریف fragment برای activity

آموزش تعریف fragment برای activity خود در فایل layout به صورت static

برای استفاده از fragment جدید خود، می توانید آن را به صورت static و به وسیله ی تگ fragment در فایل layout اضافه نمایید. خصیصه ی (attribute) android:name به کلاس مربوطه اشاره دارد.



                
    
                
    
 

استفاده از این روش برای زمانی توصیه می شود که چندین فایل layout ثابت و static برای تعریف ظاهر برنامه دارید که هریک ویژه ی یک دستگاه با config خاص طراحی شده است.

آموزش مدیریت fragment در زمان اجرا و به صورت dynamic

کلاس FragmentManager به شما این امکان را می دهد تا fragment ها را در layout activity اضافه/حذف یا جایگزین نمایید. این کلاس به وسیله ی متد getFragmentManager() قابل دسترسی می باشد. اصلاحات و تغییراتی که می خواهید اعمال کنید باید در طی یک transaction (تراکنش) به وسیله ی کلاس FragmentTransaction انجام شود. به منظور ویرایش fragment ها در یک activity می بایست یک مکان نگهدار (placeholder) FrameLayout در فایل layout تعریف نمایید.



                
                

با استفاده از FragmentManager می توانید fragment موجود در container را با fragment دیگری جایگزین نمایید.

// get fragment manager
FragmentManager fm = getFragmentManager();
// add
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.your_placehodler, new YourFragment());
// alternatively add it with a tag
// trx.add(R.id.your_placehodler, new YourFragment(), "detail");
ft.commit();
// replace
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.your_placehodler, new YourFragment());
ft.commit();
// remove
Fragment fragment = fm.findFragmentById(R.id.your_placehodler);
FragmentTransaction ft = fm.beginTransaction();
ft.remove(fragment);
ft.commit();

یک fragment جدید جایگزین fragment جاری در این container می شود.
می توانید یک تراکنش یا transaction را با استفاده از متد addToBackStack() به backstack اندروید اضافه نمایید. با این کار عملیات را به history stack از activity مورد نظر اضافه می کنید و به کاربر این امکان را می دهید تا تغییرات را از طریق دکمه ی بازگشت (back) به حالت قبلی بازگرداند.

آموزش بررسی اینکه آیا یک fragment در layout حاضر است یا خیر

به منظور بررسی اینکه آیا یک fragment در layout مورد نظر حضور دارد یا خیر، می توانید از کلاس FragmentManager کمک بگیرید. کافی است متد isLayout() را فراخوانی کرده و بررسی کنید آیا fragment مورد نظر از طریق layout به activity اضافه شده است یا خیر.

DetailFragment fragment = (DetailFragment) getFragmentManager().
        findFragmentById(R.id.detail_frag);
if (fragment==null || ! fragment.isInLayout()) {
        // start new Activity
        }
else {
        fragment.update(...);
}

آموزش بررسی تعداد fragment ها

منطق برنامه در activity به طور قطع به سناریو و شرایط جاری بستگی دارد (اینکه آیا صفحه تک قطعه است یا از چند fragment و قطعه تشکیل شده). می توان با نوشتن یک قطعه کد شرطی به تعداد fragment در اپلیکیشن پی برد. روش های متعددی برای اجرای این عملیات وجود دارد. روش اول این است که یک فایل تنظیمات/configuration در پوشه ی محتوا و منابع پروژه (values) ایجاد نمایید. جفت کلید/مقدار (key/value) به صورت پیش فرض بر روی false تنظیم می شوند. سپس یک فایل تنظیمات دیگر این مقدار را جهت سازگاری با اندازه ی دلخواه نمایشگر بر روی true قرار می دهد.
نمونه ی زیر یک فایل تنظیمات پیش فرض به نام config.xml را نمایش می دهد.


                false

حال همین فایل را در پوشه ی res/values-land با مقداری متفاوت (این بار true) ایجاد نمایید.


                true

می توانید با استفاده نوشتن دستور زیر، به اطلاعات مربوط به وضعیت جاری (state) دسترسی داشته باشید.

                    getResources().getBoolean(R.bool.twoPaneMode);

آموزش افزودن آبجکت fragment transition به backstack

می توانید آبجکت FragmentTransition را به backstack اضافه نموده و به کاربر این امکان را بدهید تا با استفاده از دکمه ی بازگشت (back) به fragment قبلی بازگردد.
برای این منظور شما می توانید متد addToBackStack() را بر روی آبجکت FragmentTransition فراخوانی کنید.

آموزش استفاده از افکت های تعریف شده توسط Property Animation API برای حرکت بین fragment ها

در طول اجرای یک تراکنش fragment، می توانید با استفاده از متد setCustomAnimations() از مجموعه توابع Property Animation، به راحتی animation تعریف نمایید (برای حرکت و انتقال بین fragment ها انیمیشن اعمال نمایید).
البته می توانید با فراخوانی متد setTranstion()، بسیاری از انیمیشن های استاندارد اندروید را برای انتقال بین fragment ها اعمال نمایید. برای پیاده سازی انیمیشن می بایست constant هایی که با FragmentTransaction.TRANSIT_FRAGMENT_* شروع می شوند را تعریف نمایید.
هر دو متد نام برده به شما امکان می دهند تا یک انیمیشن آغاز (entry animation) و یک انیمیشن پایان (exit) تعریف نمایید.

آموزش ماندگارسازی و ذخیره ی دائمی داده در fragment ها

آموزش ماندگار سازی اطلاعات جهت بازگردانی آن ها پس از راه اندازی مجدد (بین restart های برنامه)

اغلب برنامه نویس لازم دارد که داده هایی از اپلیکیشن را در حافظه به طور دائمی ذخیره کند. برای این منظور کافی است از یک فایل ساده یا دیتابیس SQLite استفاده کند.

آموزش نگهداری و بازگردانی اطلاعات پس از تغییر در تنظیمات و config

اگر می خواهید تغییرات در تنظیمات و پیکربندی را ماندگارسازی نمایید، در آن صورت می توانید از آبجکت اپلیکیشن نیز استفاده کنید.
علاوه بر روش ذکر شده، شما می توانید متد setRetainState(true) را بر روی fragment فراخوانی نمایید. با این کار اطلاعات مربوط به وضعیت نمونه ی fragment بین تغییرات در تنظیمات fragment ماندگار می شود. لازم به ذکر است که این رویکرد تنها زمانی کارگر واقع می شود که fragment ها به backstack اضافه نشده باشند. در این صورت، کافی داده ها را به عنوان یک عضو (در قالب یک فیلد) ذخیره نمایید.
توجه: Google استفاده از این متد را برای fragment هایی که UI دارند به هیچ وجه توصیه نمی کند.
می توانید با فراخوانی متد onSaveInstanceState() داده ها را در آبجکت Bundle قرار دهید. سپس می توانید این داده ها را با استفاده از متد onActivityCreated() بازیابی نمایید.

آموزش Fragment ها و پردازش در پس زمینه (background processing)

آموزش Fragment های فاقد UI/headless fragments

می توانید fragment هایی تعریف نمایید که اصلا UI و ظاهر ندارند. به این fragment ها در اصطلاح headless گفته می شود. جهت پیاده سازی چنین fragment ای کافی است مقدار null را از متد onCreateView() در fragment برگردانید.
توجه: توصیه می شود برای پردازش پس زمینه ای از service ها استفاده نمایید. اگر می خواهید این کار را از طریق fragment های خود انجام دهید، دومین راه حل پیشنهادی ما (بعد از سرویس ها) headless fragment ها همراه با فراخوانی متد setRetainInstance() هست. بدین وسیله دیگر شما مجبور نیستید تغییرات در تنظیمات را حین پردازش ناهمزمان (asynchronous processing)، خود مدیریت نمایید.

آموزش Retained headless fragment ها جهت مدیریت تغییر در تنظیمات (config changes)

Headless fragment ها معمولا به منظور کپسوله سازی (encapsulate) و نگهداری اطلاعات مربوط به وضعیت fragment ها، مابین تغییرات در تنظیمات و نحوه ی پیکربندی اپلیکیشن، استفاده می شوند. مورد کاربرد دیگری که می توان به آن اشاره کرد: برای task هایی که پردازش هایی را در پس زمینه انجام می دهند (background processing task) نیز مورد استفاده قرار می گیرند. برای کپسوله سازی داده ها در fragment ها، می توانید headless fragment را retained تعریف نمایید. retained fragment حین تغییر در تنظیمات و نحوه ی پیکربندی (configuration changes) از بین نمی روند (در واقع این fragment ها قادرند اشاره گرهایی به آبجکت های پایدار و stateful که شما می خواهید اطلاعات آن ها را حفظ نمایید، نزد خود نگه دارند).

Retained headless fragment ها جهت مدیریت تغییر در تنظیمات

برای اینکه fragment اطلاعات مربوط به وضعیت را نگه دارند و به اصطلاح retained باشند، کافی است متد setRetainInstance() را صدا بزنید.
به منظور افزودن چنین fragment ای به activity مورد نظر، می توانید متد add() از کلاس FragmentManager را فراخوانی کنید. برای اینکه در آینده به این Fragment دسترسی داشته باشید (به آن اشاره کنید)، لازم است آن را با یک تگ در فایل XML اضافه نمایید. این کار به شما اجازه می دهد، با استفاده از متد findFragmentByTag() از FragmentManager به آن دسترسی داشته باشید.
توجه داشته باشید که استفاده از متد onRetainNonConfigurationInstance() در activity دیگر منسوخ شده است و جای خود را به headless fragment retained ها داده اند.

تمرین: پیاده سازی fragment ها در اپلیکیشن ها

مبحث زیر نحوه ی استفاده از fragment ها در یک اپلیکیشن متعارف اندروید، بدون استفاده از support library (کتابخانه ی پشتیبانی از API های جدید در ویرایش های قدیمی تر اندروید) را تشریح می کند. اپلیکیشن بسته به حالت نمایش (نمای افقی/عمودی)، از layout با fragment های متفاوت بهره می گیرد.
در نمای عمودی، RssfeedActivity در لحظه تنها یک fragment را به نمایش می گذارد. از این fragment، کاربر می تواند به activity دیگری که میزبان fragment مورد نظر است راه پیدا کند. در نمای افقی، activity مزبور هر دو fragment را درکنار هم برای کاربر به نمایش می گذارد.

آموزش ایجاد پروژه

یک پروژه ی جدید اندروید ایجاد نموده و آن را به صورت زیر مقداردهی نمایید. توجه داشته باشید که نباید از لایه ی سازگاری (Compatibility layer) استفاده کنید.

جدول 1
مقدار
Property
RSS Reader
Application Name
android.example.com
Company Domain (اسم دامنه ی شرکت)
com.example.android.rssreader
Package name (اسم )
Empty Activity
Template (الگو یا قالب آماده)
RssfeedActivity
Activity
activity_rssfeed
Layout

آموزش تعریف فایل های layout برای fragment ها

یک فایل layout جدید به نام fragment_rssitem_detail.xml در پوشه یres/layout/ ایجاد نمایید.



                

توجه:

id باید صحیح باشد چرا که fragment با استفاده از این شناسه به المان TextView دسترسی دارد.
یک فایل layout جدید به نام fragment_rsslist_overview.xml ایجاد نمایید.



                

توجه:

در اینجا هم id باید دقیق باشد.

آموزش ایجاد کلاس های fragment

کلاس های زیر را ایجاد نمایید. این کلاس ها به عنوان fragment مورد استفاده قرار می گیرند. ابتدا کلاس DetailFragment را تعریف نمایید.

package com.example.android.rssreader;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailFragment extends Fragment {
    public static final String EXTRA_URL ="url";
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_rssitem_detail,
                container, false);
        return view;
    }
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null) {
            String link = bundle.getString("url");
            setText(link);
        }
    }
        public void setText(String url) {
                TextView view = (TextView) getView().findViewById(R.id.detailsText);
                view.setText(url);
        }
}

کلاس MyListFragment را ایجاد نمایید. این کلاس برخلاف اسمش، هیچ لیستی را به نمایش نمی گذارد، بلکه صرفا یک دکمه ارائه می دهد که با کلیک کاربر بر روی آن، مقدار زمان جاری به fragment ای به نام DetailsFragment ارسال می گردد.

package com.example.android.rssreader;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
public class MyListFragment extends Fragment {
    private OnItemSelectedListener listener;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_rsslist_overview,
                container, false);
        Button button = (Button) view.findViewById(R.id.updateButton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                updateDetail("fake");
            }
        });
        return view;
    }
    public interface OnItemSelectedListener {
        void onRssItemSelected(String link);
    }
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnItemSelectedListener) {
            listener = (OnItemSelectedListener) context;
        } else {
            throw new ClassCastException(context.toString()
                    + " must implement MyListFragment.OnItemSelectedListener");
        }
    }
    // triggers update of the details fragment
    public void updateDetail(String uri) {
        // create fake data
        String newTime = String.valueOf(System.currentTimeMillis());
        // send data to activity
        listener.onRssItemSelected(newTime);
    }
}

آموزش ویرایش فایل layout اصلی

فایل activity_rssfeed.xml فعلی را ویرایش نمایید.



                
    
                
    

ویرایش کلاس RssfeedActivity

کلاس RssfeedActivity را ویرایش کنید به طوری که نقش یک تابع callback (بازفراخوان) را برای ListFragment ایفا کند و متعاقبا DetailsFragment را بروز رسانی نماید.

package com.example.android.rssreader;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class RssfeedActivity extends Activity implements MyListFragment.OnItemSelectedListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rssfeed);
    }
    @Override
    public void onRssItemSelected(String link) {
        DetailFragment fragment = (DetailFragment) getFragmentManager()
                .findFragmentById(R.id.detailFragment);
        fragment.setText(link);
    }
}

آموزش تست اپلیکیشن و کسب اطمینان از اجرای موفقیت آمیز آن

اپلیکیشن خود را اجرا نمایید. هر دو fragment باید در نمای افقی و عمودی نمایش داده شوند. می توانید با استفاده از کنترل های شبیه ساز (emulator)، جهت نمایش را تغییر دهید. به دنبال فشرده شدن دکمه در ListFragment، اطلاعات DetailFragment بروز آوری می شوند.

تست اپلیکیشن و کسب اطمینان از اجرای موفقیت آمیز تمرین: نمایش fragment ها بر اساس تنظیمات و نحوه ی پیکربندی

چنانچه اپلیکیشن در نمای افقی راه اندازی شود، قاعدتا باید دو fragment (دو قطعه یا pane) را در UI برای کاربر به نمایش بگذارد. در تمرین حاضر اپلیکیشن را طوری تنظیم کنید که از این قابلیت یا رفتار پشتیبانی کند.

آموزش تعریف activity layout ویژه ی نمای عمودی

فایل activity_rssfeed.xml را با تنظیمات زیر تعریف نمایید.



                
    

آموزش تعریف یک flag یا گزینه ی بولی مستقل از resource selector (گزینشگر منابع)

یک فایل به نام config.xml در پوشه ی res/values با تنظیمات زیر ایجاد نمایید.


                false

همین فایل را در پوشه ی res/values-land با مقداری متفاوت تعریف نمایید.


                true

آموزش تنظیم و ویرایش کلاس RssfeedActivity

پیاده سازی کلاس RssfeedActivity را طوری ویرایش نمایید که در صورت قرار گرفتن اپلیکیشن در وضعیت تک قطعه/قابه (single-pane)، fragment جاری را جایگزین کند.

package com.example.android.rssreader;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.FrameLayout;
public class RssfeedActivity extends Activity implements
        MyListFragment.OnItemSelectedListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rssfeed);
        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            // all good, we use the fragments defined in the layout
            return;
        }
        // if savedInstanceState is null we do some cleanup
        if (savedInstanceState != null) {			1)
            // cleanup any existing fragments in case we are in detailed mode 
            getFragmentManager().executePendingTransactions();
            Fragment fragmentById = getFragmentManager().
                    findFragmentById(R.id.fragment_container);
            if (fragmentById!=null) {
                getFragmentManager().beginTransaction()
                        .remove(fragmentById).commit();
            }
        }
        MyListFragment listFragment = new MyListFragment();
        FrameLayout viewById = (FrameLayout) findViewById(R.id.fragment_container);
        getFragmentManager().beginTransaction()
                .replace(R.id.fragment_container, listFragment).commit();
    }
    @Override
    public void onRssItemSelected(String link) {
        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            DetailFragment fragment = (DetailFragment) getFragmentManager()
                    .findFragmentById(R.id.detailFragment);
            fragment.setText(link);
        } else {
            // replace the fragment
            // Create fragment and give it an argument for the selected article
            DetailFragment newFragment = new DetailFragment();
            Bundle args = new Bundle();
            args.putString(DetailFragment.EXTRA_URL, link);
            newFragment.setArguments(args);
            FragmentTransaction transaction = getFragmentManager().beginTransaction();
            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack so the user can navigate back
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);
            // Commit the transaction
            transaction.commit();
        }
    }
}
  1. این فرایند پاک سازی ضروری است، چراکه اپلیکیشن ما به صورت dynamic و در زمان اجرا بین دو حالت تک قطعه یا نمایش یک fragment در لحظه و نمایش دو قطعه یا fragment در آن واحد، تغییر وضعیت می دهد. البته سناریوی حاضر غیرمعمول بوده و معمولا انتخاب یک یا دو fragment برای اپلیکیشن بستگی به حداقل عرض نمایشگر دارد و این پیکربندی در زمان اجرای برنامه تغییر نمی یابد. FragmentManager سعی دارد با cache یا ذخیره ی موقتی fragment کارایی را افزایش دهد، از اینرو لازم است detailed fragment موجود را حذف نمایید.

آموزش تست اپلیکیشن و کسب اطمینان از درستی پیاده سازی آن

اپلیکیشن را اجرا نمایید. اگر اپلیکیشن را در نمای عمودی (portrait mode) اجرا کنید، طبیعتا باید تنها یک fragment را در نمایشگر مشاهده نمایید. در نمای افقی (landscape mode) شما هر دو fragment را در همزمان در UI مشاهده خواهید نمود.
حال وضعیت نمایش شبیه ساز را تغییر دهید. بر روی دکمه ی مورد نظر در نمای عمودی (portrait mode) و سپس در نمای افقی (landscape mode) کلیک نمایید. اطمینان حاصل نمایید که detail activity، زمان جاری را نمایش می دهد.

تست اپلیکیشن و کسب اطمینان از درستی پیاده سازی آن تمرین: ذخیره ی اطلاعات مربوط به وضعیت در یک headless retained fragment

در تمرین حاضر قصد دارید که آخرین انتخاب کاربر را ذخیره کرده و بیاد آورید. برای این منظور می بایست از یک headless fragment استفاده نمایید.

آموزش ایجاد headless fragment

package com.example.android.rssreader;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class SelectionStateFragment extends Fragment {
    public String lastSelection = "";
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return null;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}

آموزش ذخیره ی آخرین مقدار انتخابی در یک headless fragment

package com.example.android.rssreader;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.FrameLayout;
public class RssfeedActivity extends Activity implements
        MyListFragment.OnItemSelectedListener {
    SelectionStateFragment stateFragment;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rssfeed);
        stateFragment =
                (SelectionStateFragment) getFragmentManager()
                        .findFragmentByTag("headless");
        if(stateFragment == null) {
            stateFragment = new SelectionStateFragment();
            getFragmentManager().beginTransaction()
                    .add(stateFragment, "headless").commit();
        }
        if (findViewById(R.id.fragment_container) == null) {
            // restore state
            if (stateFragment.lastSelection.length()>0) {
                onRssItemSelected(stateFragment.lastSelection);
            }
            // all good, we use the fragments defined in the layout
            return;
        }
        // if savedInstanceState is null we do some cleanup
        if (savedInstanceState != null) {
            // cleanup any existing fragments in case we are in detailed mode 
            getFragmentManager().executePendingTransactions();
            Fragment fragmentById = getFragmentManager().
                    findFragmentById(R.id.fragment_container);
            if (fragmentById!=null) {
                getFragmentManager().beginTransaction()
                        .remove(fragmentById).commit();
            }
        }
        MyListFragment listFragment = new MyListFragment();
        FrameLayout viewById = (FrameLayout) findViewById(R.id.fragment_container);
        getFragmentManager().beginTransaction()
                .replace(R.id.fragment_container, listFragment).commit();
    }
    @Override
    public void onRssItemSelected(String link) {
        stateFragment.lastSelection = link;
        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            DetailFragment fragment = (DetailFragment) getFragmentManager()
                    .findFragmentById(R.id.detailFragment);
            fragment.setText(link);
        } else {
            // replace the fragment
            // Create fragment and give it an argument for the selected article
            DetailFragment newFragment = new DetailFragment();
            Bundle args = new Bundle();
            args.putString(DetailFragment.EXTRA_URL, link);
            newFragment.setArguments(args);
            FragmentTransaction transaction = getFragmentManager().beginTransaction();
            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack so the user can navigate back
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);
            // Commit the transaction
            transaction.commit();
        }
    }
}

آموزش تعریف یک flag یا گزینه ی بولی مستقل از گزینشگر resource

یک فایل در پوشه ی res/values به نام config.xml با تنظیمات زیر تعریف نمایید.


                false

همین فایل را در پوشه ی res/values-land با مقداری متفاوت ایجاد نمایید.


                true

آموزش تنظیم و ویرایش کلاس RssfeedActivity

بدنه ی کلاس RssfeedActivity را طوری ویرایش نمایید که در صورت قرار گرفتن اپلیکیشن در وضعیت تک قطعه (single-pane)، fragment جاری را جایگزین کند.

                    package com.example.android.rssreader;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.FrameLayout;
public class RssfeedActivity extends Activity implements
        MyListFragment.OnItemSelectedListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rssfeed);
        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            // all good, we use the fragments defined in the layout
            return;
        }
        // if savedInstanceState is null we do some cleanup
        if (savedInstanceState != null) {	1)
            // cleanup any existing fragments in case we are in detailed mode 
            getFragmentManager().executePendingTransactions();
            Fragment fragmentById = getFragmentManager().
                    findFragmentById(R.id.fragment_container);
            if (fragmentById!=null) {
                getFragmentManager().beginTransaction()
                        .remove(fragmentById).commit();
            }
        }
        MyListFragment listFragment = new MyListFragment();
        FrameLayout viewById = (FrameLayout) findViewById(R.id.fragment_container);
        getFragmentManager().beginTransaction()
                .replace(R.id.fragment_container, listFragment).commit();
    }
    @Override
    public void onRssItemSelected(String link) {
        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            DetailFragment fragment = (DetailFragment) getFragmentManager()
                    .findFragmentById(R.id.detailFragment);
            fragment.setText(link);
        } else {
            // replace the fragment
            // Create fragment and give it an argument for the selected article
            DetailFragment newFragment = new DetailFragment();
            Bundle args = new Bundle();
            args.putString(DetailFragment.EXTRA_URL, link);
            newFragment.setArguments(args);
            FragmentTransaction transaction = getFragmentManager().beginTransaction();
            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack so the user can navigate back
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);
            // Commit the transaction
            transaction.commit();
        }
    }
}
  1. این فرایند پاک سازی ضروری است، چراکه اپلیکیشن ما به صورت dynamic و در زمان اجرا بین دو حالت تک قطعه یا نمایش یک fragment در لحظه و نمایش دو قطعه یا fragment در آن واحد، تغییر وضعیت می دهد. البته سناریوی حاضر غیرمعمول بوده و معمولا انتخاب یک یا دو fragment برای اپلیکیشن بستگی به حداقل عرض نمایشگر دارد و این پیکربندی در زمان اجرای برنامه تغییر نمی یابد. FragmentManager سعی دارد با cache یا ذخیره ی موقتی fragment کارایی را افزایش دهد، از اینرو لازم است detailed fragment موجود را حذف نمایید.

آموزش تست اپلیکیشن و اعتبارسنجی رفتارهای آن

اپلیکیشن خود را اجرا نمایید. اکنون اگر اپلیکیشن خود را در نمای عمودی (portrait mode) اجرا نمایید، طبیعتا باید در لحظه تنها یک fragment در نمایشگر قابل مشاهده باشد. در نمای افقی می بایست هر دو fragment همزمان برای کاربر نمایش داده شود.
جهت یا وضعیت نمایش شبیه ساز را تغییر دهید. دکمه را در دو حالت نمای عمودی و افقی فشار داده و مطمئن شوید که detail activity زمان حاضر را به درستی نمایش می دهد.

تست اپلیکیشن و اعتبارسنجی رفتارهای آن
1395/12/05 3961 1985
رمز عبور : tahlildadeh.com یا www.tahlildadeh.com
نظرات شما

نظرات خود را ثبت کنید...