مشخصات مقاله
-
1985
-
0.0
-
3961
-
0
-
0
برنامه نویسی Multi-Pane در اندروید
این مبحث برای شما شرح می دهد چگونه می توان با استفاده از کلاس fragment، اپلیکیشن هایی با رابط کاربری مقیاس و انعطاف پذیر ایجاد نمایید.
آموزش Fragment ها
آموزش شرح Layoutهای تک قطعه (single-pane) و چند قطعه (multi-pane)
همان طور که می دانید دستگاه های اندروید دارای نمایشگرهایی با اندازه و تراکم پیکسلی متفاوت هستند.
Panel یا pane در اندروید، عبارت است از یک بخش یا قطعه از کل صفحه (UI) که کاربر با آن تعامل دارد. pane در واقع یک واژه ی کلی است که بیانگر قابلیت اندروید برای پشتیبانی از چندین view در کنار هم و در قالب یک view مرکب (واحد) بوده که ممکن است بسته به اندازه ی فضای موجود در نمایشگر از دستگاه به دستگاهی دیگر متفاوت باشد.
در صورت عدم وجود فضای کافی، تنها یک panel به نمایش در می آید. از این معمولا تحت عنوان چیدمان یا قالب (layout) تک قطعه نام برده می شود.
اما در دستگاه هایی که صفحه ی نمایش عریض تری دارند و در نتیجه فضای بیشتری در دسترس است (همچون تبلت)، طراحی چند قطعه این امکان را برای برنامه فراهم می کند تا چندین panel را همزمان به نمایش بگذارد.
آموزش شرح مفهوم 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
- fragment ها چرخه ی حیات و رفتار خود را دارند.
- می توان آن ها را در زمان اجرای اپلیکیشن و به صورت داینامیک کم و زیاد کرد.
- می توان چندین fragment را با یکدیگر ترکیب کرده و UI های چند قطعه/چند پنجره ای و انعطاف پذیر ایجاد کرد.
- این امکان نیز وجود دارد که یک fragment را در چندین activity بکار برد.
به کمک fragment می توانید برای دستگاه های کوچک UI یک قطعه ایجاد نموده و برای نمایشگرهای بزرگ رابط کاربری چند پنجره طراحی نمایید. همچنین می توانید با استفاده از fragment هر دو جهت نمایش (landscape= نمای افقی و portrait= عمودی) در گوشی ها را مدیریت نمایید.
یکی از موارد کاربرد fragment در طراحی UI را می توان در ساخت لیست مشاهده کرد. اگر بر روی یک آیتم از لیست کلیک کنید، در صفحه ی تبلت، جزئیات مربوطه در همان صفحه، برای مثال در سمت راست به نمایش در می آیند. اما همین لیست در نمایشگر موبایل، کاربر را به صفحه ی مجزا حاوی جزئیات مرتبط هدایت می کند.
برای این مبحث اپلیکیشن شما باید دو fragment داشته باشد: main و detail. البته شما می توانید بسته به نیاز خود fragment های بیشتری تعریف نمایید. بعلاوه اپلیکیشن شما می بایست دو activity به شرح زیر داشته باشد: main activity و detailed activity. در نمایشگر تبلت main activity هر دو fragment را در layout خود به نمایش می گذارد. این در حالی است که main activity در صفحه نمایش موبایل، فقط main fragment را در لحظه به نمایش می گذارد.
آموزش طراحی اپلیکیشنی با UI انعطاف پذیر و سازگار با نمایشگرهای مختلف به وسیله ی fragment ها
Fragment ها را می توان به صورت static تعریف کنید، بدین معنی که به وسیله ی تگ
اینکه کدام گزینه را انتخاب کنید کاملا به نیاز و مورد استفاده ی شما بستگی دارد. به طور معمول ویرایش fragment در زمان اجرا منعطف تر است، اما پیاده سازی آن کمی پیچیده تر می باشد. به منظور تعریف یک fragment جدید می توانید از کلاس android.app.Fragment و یا یکی از کلاس های فرزند (subclass) آن ارث بری (extend) نمایید. کلاس های فرزندی که می توانید مورد استفاده قرار دهید عبارتند از: ListFragment، DialogFragment، PreferenceFragment یا WebViewFragment. کد زیر یک نمونه پیاده سازی را نمایش می دهد:
برای اینکه بتوان امکان استفاده ی مجدد از fragment ها را افزایش داد و به اصطلاح آن ها را بازیافت نمود، لازم است هر گونه تعامل بین این fragment ها منحصرا از طریق activity میزبان انجام شود. به بیان دیگر، fragment ها نباید مستقیما و بدون واسطه با یکدیگر ارتباط داشته باشند. Activity می تواند یک bundle را به عنوان پارامتر به fragment ارسال کند. fragment آبجکت bundle را در متد onActivityCreated خود دریافت می کند. در اندروید هر fragment چرخه ی حیات مختص به خود را دارد. با این وجود چرخه ی حیات آن همیشه به activity میزبان آن متصل است. زمانی که activity متوقف می شود، همگام با آن fragment های زیرمجموعه ی این activity نیز متوقف می شوند. اگر activity از حافظه کلا پاک شود (destroy)، fragment های آن نیز همزمان از بین می روند. برای استفاده از fragment جدید خود، می توانید آن را به صورت static و به وسیله ی تگ fragment در فایل layout اضافه نمایید. خصیصه ی (attribute) android:name به کلاس مربوطه اشاره دارد. استفاده از این روش برای زمانی توصیه می شود که چندین فایل layout ثابت و static برای تعریف ظاهر برنامه دارید که هریک ویژه ی یک دستگاه با config خاص طراحی شده است. کلاس FragmentManager به شما این امکان را می دهد تا fragment ها را در layout activity اضافه/حذف یا جایگزین نمایید. این کلاس به وسیله ی متد getFragmentManager() قابل دسترسی می باشد. اصلاحات و تغییراتی که می خواهید اعمال کنید باید در طی یک transaction (تراکنش) به وسیله ی کلاس FragmentTransaction انجام شود. به منظور ویرایش fragment ها در یک activity می بایست یک مکان نگهدار (placeholder) FrameLayout در فایل layout تعریف نمایید. با استفاده از FragmentManager می توانید fragment موجود در container را با fragment دیگری جایگزین نمایید.
یک fragment جدید جایگزین fragment جاری در این container می شود. به منظور بررسی اینکه آیا یک fragment در layout مورد نظر حضور دارد یا خیر، می توانید از کلاس FragmentManager کمک بگیرید. کافی است متد isLayout() را فراخوانی کرده و بررسی کنید آیا fragment مورد نظر از طریق layout به activity اضافه شده است یا خیر.
منطق برنامه در activity به طور قطع به سناریو و شرایط جاری بستگی دارد (اینکه آیا صفحه تک قطعه است یا از چند fragment و قطعه تشکیل شده). می توان با نوشتن یک قطعه کد شرطی به تعداد fragment در اپلیکیشن پی برد. روش های متعددی برای اجرای این عملیات وجود دارد. روش اول این است که یک فایل تنظیمات/configuration در پوشه ی محتوا و منابع پروژه (values) ایجاد نمایید. جفت کلید/مقدار (key/value) به صورت پیش فرض بر روی false تنظیم می شوند. سپس یک فایل تنظیمات دیگر این مقدار را جهت سازگاری با اندازه ی دلخواه نمایشگر بر روی true قرار می دهد. حال همین فایل را در پوشه ی res/values-land با مقداری متفاوت (این بار true) ایجاد نمایید. می توانید با استفاده نوشتن دستور زیر، به اطلاعات مربوط به وضعیت جاری (state) دسترسی داشته باشید.
می توانید آبجکت FragmentTransition را به backstack اضافه نموده و به کاربر این امکان را بدهید تا با استفاده از دکمه ی بازگشت (back) به fragment قبلی بازگردد.
در طول اجرای یک تراکنش fragment، می توانید با استفاده از متد setCustomAnimations() از مجموعه توابع Property Animation، به راحتی animation تعریف نمایید (برای حرکت و انتقال بین fragment ها انیمیشن اعمال نمایید). اغلب برنامه نویس لازم دارد که داده هایی از اپلیکیشن را در حافظه به طور دائمی ذخیره کند. برای این منظور کافی است از یک فایل ساده یا دیتابیس SQLite استفاده کند.
اگر می خواهید تغییرات در تنظیمات و پیکربندی را ماندگارسازی نمایید، در آن صورت می توانید از آبجکت اپلیکیشن نیز استفاده کنید.
می توانید fragment هایی تعریف نمایید که اصلا UI و ظاهر ندارند. به این fragment ها در اصطلاح headless گفته می شود. جهت پیاده سازی چنین fragment ای کافی است مقدار null را از متد onCreateView() در fragment برگردانید. Headless fragment ها معمولا به منظور کپسوله سازی (encapsulate) و نگهداری اطلاعات مربوط به وضعیت fragment ها، مابین تغییرات در تنظیمات و نحوه ی پیکربندی اپلیکیشن، استفاده می شوند. مورد کاربرد دیگری که می توان به آن اشاره کرد: برای task هایی که پردازش هایی را در پس زمینه انجام می دهند (background processing task) نیز مورد استفاده قرار می گیرند. برای کپسوله سازی داده ها در fragment ها، می توانید headless fragment را retained تعریف نمایید. retained fragment حین تغییر در تنظیمات و نحوه ی پیکربندی (configuration changes) از بین نمی روند (در واقع این fragment ها قادرند اشاره گرهایی به آبجکت های پایدار و stateful که شما می خواهید اطلاعات آن ها را حفظ نمایید، نزد خود نگه دارند).
برای اینکه fragment اطلاعات مربوط به وضعیت را نگه دارند و به اصطلاح retained باشند، کافی است متد setRetainInstance() را صدا بزنید.
مبحث زیر نحوه ی استفاده از fragment ها در یک اپلیکیشن متعارف اندروید، بدون استفاده از support library (کتابخانه ی پشتیبانی از API های جدید در ویرایش های قدیمی تر اندروید) را تشریح می کند. اپلیکیشن بسته به حالت نمایش (نمای افقی/عمودی)، از layout با fragment های متفاوت بهره می گیرد. یک پروژه ی جدید اندروید ایجاد نموده و آن را به صورت زیر مقداردهی نمایید. توجه داشته باشید که نباید از لایه ی سازگاری (Compatibility layer) استفاده کنید. یک فایل layout جدید به نام fragment_rssitem_detail.xml در پوشه یres/layout/ ایجاد نمایید.
id باید صحیح باشد چرا که fragment با استفاده از این شناسه به المان TextView دسترسی دارد. در اینجا هم id باید دقیق باشد. کلاس های زیر را ایجاد نمایید. این کلاس ها به عنوان fragment مورد استفاده قرار می گیرند. ابتدا کلاس DetailFragment را تعریف نمایید. کلاس MyListFragment را ایجاد نمایید. این کلاس برخلاف اسمش، هیچ لیستی را به نمایش نمی گذارد، بلکه صرفا یک دکمه ارائه می دهد که با کلیک کاربر بر روی آن، مقدار زمان جاری به fragment ای به نام DetailsFragment ارسال می گردد. فایل activity_rssfeed.xml فعلی را ویرایش نمایید. کلاس RssfeedActivity را ویرایش کنید به طوری که نقش یک تابع callback (بازفراخوان) را برای ListFragment ایفا کند و متعاقبا DetailsFragment را بروز رسانی نماید. اپلیکیشن خود را اجرا نمایید. هر دو fragment باید در نمای افقی و عمودی نمایش داده شوند. می توانید با استفاده از کنترل های شبیه ساز (emulator)، جهت نمایش را تغییر دهید. به دنبال فشرده شدن دکمه در ListFragment، اطلاعات DetailFragment بروز آوری می شوند. چنانچه اپلیکیشن در نمای افقی راه اندازی شود، قاعدتا باید دو fragment (دو قطعه یا pane) را در UI برای کاربر به نمایش بگذارد. در تمرین حاضر اپلیکیشن را طوری تنظیم کنید که از این قابلیت یا رفتار پشتیبانی کند. فایل activity_rssfeed.xml را با تنظیمات زیر تعریف نمایید. یک فایل به نام config.xml در پوشه ی res/values با تنظیمات زیر ایجاد نمایید. همین فایل را در پوشه ی res/values-land با مقداری متفاوت تعریف نمایید. پیاده سازی کلاس RssfeedActivity را طوری ویرایش نمایید که در صورت قرار گرفتن اپلیکیشن در وضعیت تک قطعه/قابه (single-pane)، fragment جاری را جایگزین کند.
اپلیکیشن را اجرا نمایید. اگر اپلیکیشن را در نمای عمودی (portrait mode) اجرا کنید، طبیعتا باید تنها یک fragment را در نمایشگر مشاهده نمایید. در نمای افقی (landscape mode) شما هر دو fragment را در همزمان در UI مشاهده خواهید نمود. در تمرین حاضر قصد دارید که آخرین انتخاب کاربر را ذخیره کرده و بیاد آورید. برای این منظور می بایست از یک headless fragment استفاده نمایید. یک فایل در پوشه ی res/values به نام config.xml با تنظیمات زیر تعریف نمایید. همین فایل را در پوشه ی res/values-land با مقداری متفاوت ایجاد نمایید. بدنه ی کلاس RssfeedActivity را طوری ویرایش نمایید که در صورت قرار گرفتن اپلیکیشن در وضعیت تک قطعه (single-pane)، fragment جاری را جایگزین کند.
اپلیکیشن خود را اجرا نمایید. اکنون اگر اپلیکیشن خود را در نمای عمودی (portrait mode) اجرا نمایید، طبیعتا باید در لحظه تنها یک fragment در نمایشگر قابل مشاهده باشد. در نمای افقی می بایست هر دو fragment همزمان برای کاربر نمایش داده شود.
در فایل layout می توانید مشخص کنید که activity از چندین fragment و قطعه تشکیل شود.
همان طور که گفته شد، شما می توانید fragment های یک activity را در زمان اجرا ویرایش کنید (کم یا زیاد کنید( که به آن تعریف به صورت پویا و dynamic definition گفته می شود.
برای اینکه بتوانید با توجه به فضای موجود در نمایشگر خود چند fragment را نمایش دهید، کافی است بر اساس یکی از روش های زیر اقدام نمایید:
آموزش تعریف و استفاده از fragment ها
آموزش تعریف 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 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 می بایست یک 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 ها
detailFragment = new DetailFragment();
// configure link
Bundle bundle = new Bundle();
bundle.putString("link", link);
detailFragment.setArguments(bundle);
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Bundle bundle = getArguments();
if (bundle != null) {
setText(bundle.getString("link"));
}
}
آموزش چرخه ی حیات (life cycle) fragment
متد قرار می دهید که از fragment برای مقداردهی بیشتر استفاده می کند.
در واقع در این مرحله، fragment ایجاد شده است. این متد بعد از فراخوانده شدن onCreate() مربوط به activity و قبل از متد onCreateView() مربوط به fragment صدا خورده می شود.
در این متد نباید با activity تعامل داشته باشید. Activity هنوز کاملا ساخته و مقداردهی اولیه نشده است.
لزومی ندارد این متد را برای fragment های فاقد UI/headless فراخوانی کنید. view های inflate شده بخشی از زنجیره ی view های activity میزبان محسوب می شوند.
به عبارت دیگر، هنگامی که fragment می خواهد view های خود را بسازد این متد را صدا می زند. به منظور پیاده سازی UI یا رابط کاری، برنامه نویس می بایست یک آبجکت view که
پوشه ی حاوی fragment را مشخص می کند، به عنوان خروجی این متد تعریف کنید. اگر fragment رابط کاربری نداشته باشد، می توانید خروجی متد را null تعریف کنید.
نمونه ی Activity و fragment همراه با زنجیره ی view های activity ساخته شده اند. در این برهه، می توان با فراخوانی متد findViewById() به view دلخواه دسترسی داشت.
در این متد شما می توانید از آبجکت هایی که به یک آبجکت Context نیاز دارند، نمونه سازی نمایید.
به عبارت دیگر، این متد بعد از onCreateView() و هنگامی که activity میزبان ایجاد شده، call می شود. activity و fragment به صورت یک آبجکت view سلسله مراتبی برای activity تولید می شوند.
متد onStart() زمانی صدا زده می شود که fragment نمایان و قابل مشاهده شود.
Fragment با کاربر تعاملی ندارد. حال یا activity آن در حال متوقف شدن است یا یک عملیات مربوط به fragment در حال ویرایش آن در activity است.
آموزش تعریف fragment برای activity
آموزش تعریف fragment برای activity خود در فایل layout به صورت static
آموزش مدیریت fragment در زمان اجرا و به صورت dynamic
// 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();
می توانید یک تراکنش یا transaction را با استفاده از متد addToBackStack() به backstack اندروید اضافه نمایید. با این کار عملیات را به history stack از activity مورد نظر اضافه می کنید و به کاربر این امکان را می دهید تا تغییرات را از طریق دکمه ی بازگشت (back) به حالت قبلی بازگرداند.
آموزش بررسی اینکه آیا یک fragment در layout حاضر است یا خیر
DetailFragment fragment = (DetailFragment) getFragmentManager().
findFragmentById(R.id.detail_frag);
if (fragment==null || ! fragment.isInLayout()) {
// start new Activity
}
else {
fragment.update(...);
}
آموزش بررسی تعداد fragment ها
نمونه ی زیر یک فایل تنظیمات پیش فرض به نام config.xml را نمایش می دهد.
getResources().getBoolean(R.bool.twoPaneMode);
آموزش افزودن آبجکت fragment transition به backstack
برای این منظور شما می توانید متد addToBackStack() را بر روی آبجکت FragmentTransition فراخوانی کنید.
آموزش استفاده از افکت های تعریف شده توسط Property Animation API برای حرکت بین fragment ها
البته می توانید با فراخوانی متد setTranstion()، بسیاری از انیمیشن های استاندارد اندروید را برای انتقال بین fragment ها اعمال نمایید. برای پیاده سازی انیمیشن می بایست constant هایی که با FragmentTransaction.TRANSIT_FRAGMENT_* شروع می شوند را تعریف نمایید.
هر دو متد نام برده به شما امکان می دهند تا یک انیمیشن آغاز (entry animation) و یک انیمیشن پایان (exit) تعریف نمایید.
آموزش ماندگارسازی و ذخیره ی دائمی داده در fragment ها
آموزش ماندگار سازی اطلاعات جهت بازگردانی آن ها پس از راه اندازی مجدد (بین restart های برنامه)
آموزش نگهداری و بازگردانی اطلاعات پس از تغییر در تنظیمات و config
علاوه بر روش ذکر شده، شما می توانید متد setRetainState(true) را بر روی fragment فراخوانی نمایید. با این کار اطلاعات مربوط به وضعیت نمونه ی fragment بین تغییرات در تنظیمات fragment ماندگار می شود. لازم به ذکر است که این رویکرد تنها زمانی کارگر واقع می شود که fragment ها به backstack اضافه نشده باشند. در این صورت، کافی داده ها را به عنوان یک عضو (در قالب یک فیلد) ذخیره نمایید.
توجه: Google استفاده از این متد را برای fragment هایی که UI دارند به هیچ وجه توصیه نمی کند.
می توانید با فراخوانی متد onSaveInstanceState() داده ها را در آبجکت Bundle قرار دهید. سپس می توانید این داده ها را با استفاده از متد onActivityCreated() بازیابی نمایید.
آموزش Fragment ها و پردازش در پس زمینه (background processing)
آموزش Fragment های فاقد UI/headless fragments
توجه: توصیه می شود برای پردازش پس زمینه ای از service ها استفاده نمایید. اگر می خواهید این کار را از طریق fragment های خود انجام دهید، دومین راه حل پیشنهادی ما (بعد از سرویس ها) headless fragment ها همراه با فراخوانی متد setRetainInstance() هست. بدین وسیله دیگر شما مجبور نیستید تغییرات در تنظیمات را حین پردازش ناهمزمان (asynchronous processing)، خود مدیریت نمایید.
آموزش Retained headless fragment ها جهت مدیریت تغییر در تنظیمات (config changes)
به منظور افزودن چنین fragment ای به activity مورد نظر، می توانید متد add() از کلاس FragmentManager را فراخوانی کنید. برای اینکه در آینده به این Fragment دسترسی داشته باشید (به آن اشاره کنید)، لازم است آن را با یک تگ در فایل XML اضافه نمایید. این کار به شما اجازه می دهد، با استفاده از متد findFragmentByTag() از FragmentManager به آن دسترسی داشته باشید.
توجه داشته باشید که استفاده از متد onRetainNonConfigurationInstance() در activity دیگر منسوخ شده است و جای خود را به headless fragment retained ها داده اند.
در نمای عمودی، RssfeedActivity در لحظه تنها یک fragment را به نمایش می گذارد. از این fragment، کاربر می تواند به activity دیگری که میزبان fragment مورد نظر است راه پیدا کند. در نمای افقی، activity مزبور هر دو fragment را درکنار هم برای کاربر به نمایش می گذارد.
آموزش ایجاد پروژه
آموزش تعریف فایل های layout برای fragment ها
یک فایل layout جدید به نام fragment_rsslist_overview.xml ایجاد نمایید.
آموزش ایجاد کلاس های 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 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);
}
}
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 اصلی
ویرایش کلاس RssfeedActivity
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 ها بر اساس تنظیمات و نحوه ی پیکربندی
آموزش تعریف activity layout ویژه ی نمای عمودی
آموزش تعریف یک flag یا گزینه ی بولی مستقل از resource selector (گزینشگر منابع)
آموزش تنظیم و ویرایش کلاس RssfeedActivity
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();
}
}
}
آموزش تست اپلیکیشن و کسب اطمینان از درستی پیاده سازی آن
حال وضعیت نمایش شبیه ساز را تغییر دهید. بر روی دکمه ی مورد نظر در نمای عمودی (portrait mode) و سپس در نمای افقی (landscape mode) کلیک نمایید. اطمینان حاصل نمایید که detail activity، زمان جاری را نمایش می دهد.
تمرین: ذخیره ی اطلاعات مربوط به وضعیت در یک headless retained 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
آموزش تنظیم و ویرایش کلاس RssfeedActivity
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();
}
}
}
آموزش تست اپلیکیشن و اعتبارسنجی رفتارهای آن
جهت یا وضعیت نمایش شبیه ساز را تغییر دهید. دکمه را در دو حالت نمای عمودی و افقی فشار داده و مطمئن شوید که detail activity زمان حاضر را به درستی نمایش می دهد.