أندرويد للمبتدئين كيفية استخدام التجزئة لتحقيق أفضل استفادة من أحجام الشاشات المختلفة في تطبيقات أندرويد


أحمد النوبي

تنتشر الهواتف المحمولة والحواسيب اللوحية العاملة بنظام تشغيل أندرويد في أحجام مختلفة ودقة شاشات مختلفة، لذلك من الأفضل دائمًا إذا استطاع التطبيق أن يتأقلم مع حجم ودقة الشاشة لتقديم أفضل تجربة استخدام مع الاستغلال الأمثل لشاشة.

android-fragment.png

ويمكن أن تبني تطبيقًا تتغير الواجهة الخاصة به بشكل ديناميكي تبعًا للمساحة المتاحة في الشاشة، فإذا لم تتواجد مساحة كافية، يظهر جزء من الواجهة فقط في الشاشة ومنه يمكن الانتقال للواجهة الأخرى مثل الصورة التالية:

1.png

أما إذا توافرت المساحة الكافية فيمكن دمج أكثر من واجهة في واجهة واحدة لسهولة التنقل بينها كما في الصورة التالية:

2.png

لذا يوفر أندرويد عدة طرق للقيام بذلك من ضمنها استخدام الـ Fragment داخل التطبيق.

Fragment

يُعبر مصطلح Fragment عن تجزئة واجهة المستخدم إلى وحدات أصغر تُمكن المطوّر من دمج أو فصل هذه الوحدات تبعًا لحجم الشاشة، ولا تتكون تلك الوحدة من عناصر واجهة المستخدم فقط ولكنها أيضًا تتكون من شيفرات تتحكم في وظيفة هذه الواجهة وسلوكها.

لكن هذه الوحدات ليست مستقلة بذاتها أي لا يمكن أن تُعرض بداخل التطبيق إلا من خلال نشاط “Activity” يستضيفها بداخله. ولهذا المفهوم العديد من المزايا التي توفرها عند تطوير التطبيقات ومنها:

  1. تقسيم الشيفرات المعقدة التي تتواجد في واجهة نشاط واحد إلى عدة وحدات لكل منها شيفرة بسيطة يتم دمجها وترتيبها بعد ذلك داخل واجهة واحدة.
  2. إمكانية إعادة استخدام الوحدة عدة مرات في أنشطة مختلفة ودمجها مع وحدات أخرى مختلفة.
  3. التكييف مع الواجهات المختلفة والأحجام المختلفة.

3.png

دورة حياة التجزئة Fragment

للـ Fragment دورة حياة مشابهة لدورة حياة النشاط ويتم استدعاء التوابع الخاصة بدورة الحياة عند حدوث حدث معين كإنشاء أو إزالة الـ Fragment.

4.png

  • ()onAttach: ويتم استدعاؤه عندما يتم ربط الـ Fragment بالنشاط المضيف.
  • ()onCreate: ويتم استدعاؤه بعد ربطه بالنشاط مباشرة وذلك لاستخدامها في تهيئة العناصر والمتغيرات التي نرغب في بقائها حتى بعد إزالة واجهة الـ Fragment من داخل النشاط المضيف.
  • ()onCreateView: ويتم استدعاؤه لربط الواجهة الخاصة بالـ Fragment بالشيفرة الخاصة به.
  • ()onActivityCreated: ويتم استدعاؤه بعد انتهاء النشاط المضيف من التابع ()onCreate الخاص به.
  • ()onStart: ويتم استدعاؤه عندما تبدأ الواجهة الخاصة بالـ Fragment في الظهور وذلك بعد الانتهاء من ()onStart الخاصة بالنشاط أولًا.
  • ()onResume: ويتم استدعاؤه عندما تظهر الواجهة الخاصة بالـFragment وتصبح قابلة لتفاعل المستخدم معها، مع العلم بأنه لا يمكن للمستخدم بالتفاعل مع واجهة الـ Fragment قبل أن يتم استدعاء ()onResume الخاصة بالنشاط أولًا لتصبح واجهة النشاط قابلة للتفاعل أيضًا.
  • ()onPause: ويتم استدعاؤه عندما تبدأ الواجهة الخاصة بالنشاط المضيف في الاختفاء أو تظهر بشكل جزئي ولا يمكن للمستخدم التفاعل معها لوجود شيء ما يحجبها، أو عند الاستعداد لاستبدال أو إزالة الـ Fragment.
  • ()onStop: ويتم استدعاؤه عندما تختفي الواجهة الخاصة بالنشاط المضيف من أمام المستخدم أو لاستبدال أو إزالة الـ Fragment.
  • ()onDestroyView: ويتم استدعاؤه عند إزالة الواجهة التي تم إضافتها من قبل في ()onCreateView.
  • ()onDestroy: ويتم استدعاؤه قبل تدمير الواجهة وإزالتها من الذاكرة.
  • ()onDetach: ويتم استدعاؤه عند إزالة الـ Fragment من واجهة النشاط المضيف.

لذا فكما ذكرنا فدورة حياة الـ Fragment مشابهة لدورة حياة النشاط ولكن هناك بعض التوابع الخاصة بالـ Fragment فقط ولا تتواجد في النشاط.

تطبيق 1

الهدف من هذا التطبيق فهم الأساسيات الخاصة بالـ Fragments حيث تتكون من:

  1. واجهة خاصة به (XML File).
  2. صنف جديد يرث من Fragment.
  3. مكان خاص له في الواجهة الرئيسية الخاصة بالنشاط المضيف.

5.png

من Android Studio نُنشئ مشروعًا جديدًا يُدعى Simple Fragment وبنفس الإعدادات في المشاريع السابقة.

نبدأ أولًا بصنع Fragment جديد (ملف واجهة وملف للشيفرة) عن طريق الضغط بالزر الأيمن على اسم الحزمة الخاصة بالمشروع كما بالصورة التالية:

6.png

ثم اختر:

New > Fragment > Fragment Blank

لتظهر لك هذه النافذة، قم بتغيير الاسم إلى الاسم الذي تراه مناسبًا وتغيير الاختيارات كما بالصورة:

img1.png

ثم اضغط Finish.

سيقوم Android Studio بعد ذلك بإنشاء صنف جديد يُدعى ExampleFragment ويرث من الصنف Fragment، كما سينشئ ملف واجهة جديد يُدعى fragment_example.

نقوم بتغيير ملف الواجهة كما كنا نفعل في التطبيقات السابقة وفي هذا المثال سنكتفي بوضع نص في منتصف الواجهة مع تغيير لون الخلفية:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:background="#ccefff"
	android:orientation="vertical">

	<TextView
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:text="Hello From Example Fragment"
		android:gravity="center"/>
</LinearLayout>

والآن لربط ملف الواجهة بالشيفرة الخاصة بالصنف ExampleFragment نكتب داخل التابع ()onCreateView -وهو التابع المسؤول عن ربط الشيفرة بالواجهة- الشيفرة التالية:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

	View rootView =inflater.inflate(R.layout.fragment_example, container, false);
	return rootView;
}

تختلف طريقة ربط الواجهة في الـ Fragment عنها في النشاط (Activity) فلا نستطيع استخدام التابع ()setContentView، عوضًا عن ذلك نستخدم الكائن inflater الذي تم تمريره للتابع ()onCreateView ونستدعي به التابع ()inflate وقد تعاملنا مع هذا التابع من قبل في الدرس الخاص بقوائم العرض ListView، ونمرر للتابع المعاملات الآتية:

  1. الواجهة التي نريد ربطها.
  2. الواجهة التي ستحتوي واجهة الـ Fragment بداخلها وهي هنا container.
  3. قيمة منطقية تُعبر عن "هل نريد وضع واجهة الـ Fragment داخل الواجهة container -المعامل السابق- أم لا؟"، وسنقوم دائمًا عند التعامل مع الـ Fragments بالإجابة بلا أو القيمة المنطقية false؛ وذلك لأنها بشكل افتراضي يتم ربطها.

ويقوم هذا التابع بتحويل ملف الواجهة (XML File) إلى كائن من الصنف View يمكن التعامل معه داخل شيفرات جافا بسهولة.

بعد ذلك نعيد الكائن rootView إلى التابع، لتصبح الشيفرة النهائية على الشّكل التّالي:

package apps.noby.simplefragment;

import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class ExampleFragment extends Fragment {

  	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

		View rootView =inflater.inflate(R.layout.fragment_example, container, false);
		return rootView;
	}
}

لاحظ أنه لكي تعمل الشيفرة بشكل سليم عند الوراثة من الصنف Fragment يجب أن تقوم بتضمين ;import android.app.Fragment.

يتبقى الآن الخطوة الأخيرة وهي إيجاد مكان لهذا الـ Fragment داخل النشاط المضيف له، وفي هذا التطبيق النشاط المضيف هو النشاط الرئيسي (MainActivity)، ولوضع مكان للـ Fragment داخل ملف واجهة النشاط نقوم بتعريف عنصر واجهة جديد من النوع <fragment> وتحديد له بعض الخصائص كما يلي:

<fragment
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:name="apps.noby.simplefragment.ExampleFragment"
	android:id="@+id/fragment_container"
	tools:layout="@layout/fragment_example" />

هناك بعض الخصائص المشتركة في العناصر كتحديد الطول والعرض وهناك خصائص تميز العنصر ولابد من تحديدها مثل android:name وتكمن أهميته في تحديد الصنف الذي سيُعرض داخل هذا العنصر ولن يعمل التطبيق بدون وضع قيمة لهذه الخاصية وهنا وضعنا اسم الصنف الذي صنعناه سابقًا مع وضع اسم الحزمة كاملًا قبله. ولابد أيضًا من تحديد id مميز لهذا العنصر.

وسنضع داخل ملف الواجهة الرئيسي نص قبل واجهة الـ Fragment ليصبح ملف الواجهة كالتالي:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical">

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Hello Main Activity!"
		android:layout_gravity="center"
		android:textSize="25sp"/>

	<fragment
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:name="apps.noby.simplefragment.ExampleFragment"
		android:id="@+id/fragment_container"
		tools:layout="@layout/fragment_example" />
</LinearLayout>

ولن نقوم بتغيير الشيفرة الخاصة بالنشاط الرئيسي وسندعها كما هي، الآن يمكننا تجربة التطبيق على المحاكي للتأكد من عمله كما ينبغي.

8.png

وتُسمى هذه الطريقة بالطريقة الساكنة لتضمين الـ Fragment؛ وذلك لأننا لم نحتج إلى كتابة شيفرات داخل النشاط لوضع الـ Fragment بداخله.

تطبيق 2

في هذا التطبيق سنقوم بعرض أكثر من Fragment بداخل نشاط واحد عند توفر المساحة المناسبة له، أما إذا لم تتوفر فسيتم عرض Fragment واحد فقط.

9.png

سنقوم بتعديل على المثال السابق وعمل Fragment جديد يُدعى DetailsFragment بنفس الطريقة السابقة ونجعل له الواجهة التالية:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:background="#ffc829"
	android:orientation="vertical">

  <TextView
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:text="Hello From Details Fragment"
		android:gravity="center"/>
</LinearLayout>

وتتشابه الواجهة السابقة مع واجهة الـ Fragment الآخر، بعد ذلك سنقوم بعمل ملف واجهة جديد يُدعى activity_main ولكن سنختار أن يوضع في المجلد layout-large كما في الصورة التالية:

10.png

وبداخل ملف الواجهة الجديد نضع مكان آخر للـ Fragment الجديد بجانب الأول.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical">

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Hello Main Activity!"
		android:layout_gravity="center"
		android:textSize="25sp"/>

	<LinearLayout
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:orientation="horizontal">

		<fragment
			android:layout_width="0dp"
			android:layout_height="match_parent"
			android:layout_weight="1"
			android:name="apps.noby.simplefragment.ExampleFragment"
			android:id="@+id/fragment_container"
			tools:layout="@layout/fragment_example" />

		<fragment
			android:layout_width="0dp"
			android:layout_height="match_parent"
			android:layout_weight="1"
			android:name="apps.noby.simplefragment.DetailsFragment"
			android:id="@+id/fragment_details"
			tools:layout="@layout/fragment_details" />

	</LinearLayout>
</LinearLayout>

ووضع القيم الخاصة بالخاصيتين name و id، والشيفرة الخاصة بملف جافا الخاص بـ DetailsFragment يتشابه مع ExampleFragment.

package apps.noby.simplefragment;


import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


public class DetailsFragment extends Fragment {

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

		View rootView = inflater.inflate(R.layout.fragment_details,container,false);
		return rootView;
	}
}

نستطيع الآن تجربة التطبيق بعد إجراء هذا التعديل على محاكي للهاتف وآخر للجهاز اللوحي -إذا لم تكن صنعت محاكيًا للحاسب اللوحي فينبغي أن تصنع واحدًا قبل التجربة-.

11.png

بناءً على الحجم الخاص بشاشة الجهاز يقوم التطبيق باستدعاء ملف الواجهة الخاص بالنشاط المناسب (العادي أو الكبير).

لاحظ أنه يجب أن يكون بجانب اسم المجلد layout كلمة large حتى يعلم التطبيق بوجود ملفات خاصة بالشاشات ذات الحجم الكبير.

تطبيق 3

في هذا التطبيق سنقوم بتغيير طريقة تضمين الـ Fragment من الطريقة الساكنة إلى الديناميكية أي إمكانية إضافة، تبديل حذف الـ Fragment أثناء تشغيل التطبيق والتحكم به عن طريق الشيفرة.

وللقيام بذلك سنستبدل عنصر الواجهة <fragment> بالعنصر <FrameLayout> والذي يقوم بحجز مساحة فارغة في الواجهة سيتم تحديد فيما بعد ما الذي سيشغلها.

كل ما سيختلف في هذا التطبيق ملفات الواجهة الرئيسية الخاصة بالنشاط المضيف فقط ولن يحدث تغيير في الواجهة الخاصة بالـ Fragment.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical">

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Hello Main Activity!"
		android:layout_gravity="center"
		android:textSize="25sp"/>

	<FrameLayout
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:id="@+id/view_container" />
</LinearLayout>

ولتحديد المحتوى الخاص بالعنصر FrameLayout ننتقل إلى ملف النشاط MainActivity.java لتعديله، الآن لإضافة Fragment جديد داخل الواجهة ينبغي أن نمر بثلاث خطوات

1. إيجاد كائن من الصنف FragmentManger والذي يستطيع إدارة كافة المهام الخاصة بالتعامل مع الـ Fragments ومن ضمن هذه المهام هي إضافة الـ Framgments ولجلب كائن من هذا الصنف نستدعى التابع ()getFragmentManager حيث يوجد كائن بالفعل داخل النشاط ويكتفي استدعاءه فقط.

FragmentManager fragmentManager = getFragmentManager();

2. للتحكم في المهام الخاصة بإضافة أو تبديل او إزالة الـ Fragments نحتاج إلى كائن من الصنف FragmentTransaction والذي يحتوي على التوابع التي تقوم بعملية الإضافة تلك.

FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

3. أخيرًا نستطيع استدعاء التابع المناسب للعملية وتمرير له المعاملات الصحيحة.

ExampleFragment frag = new ExampleFragment();
fragmentTransaction.add(R.id.view_container, frag);
fragmentTransaction.commit();

يستطيع التابع ()add من إضافة Fragment إلى الواجهة التي سيُعرض بها، وتلك هي المعاملات التي نمررها له:

  • الـ id الخاص بعنصر الواجهة.
  • كائن من الـ Fragment الذي نرغب في عرضه بداخل عنصر الواجهة.

وبعد ذلك نستدعي التابع ()commit لتنفيذ العملية السابقة، حيث لن يتم تنفيذها إلا بعد استدعاءه.

لتصبح الشيفرة الكاملة للنشاط كما يلي:

package apps.noby.simplefragment;

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		if(findViewById(R.id.view_container) != null){
			FragmentManager fragmentManager = getFragmentManager();
			FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
			ExampleFragment frag = new ExampleFragment();
			fragmentTransaction.add(R.id.view_container, frag);
			fragmentTransaction.commit();
		}
	}
}

وفائدة الجملة الشرطية هو التأكد من أننا نتعامل مع ملف الواجهة activity_main.xml الخاص بالهواتف والذي يحتوي بداخله على عنصر له id قيمته view_container، وليس ملف الواجهة الآخر الخاص بالشاشات كبيرة الحجم حيث لا يحتوي على عنصر له ذلك الـ id.

والآن بتجربة التطبيق على كلا المحاكيين -الهاتف والحاسب اللوحي- نجد أنه يعمل كالتطبيقات السابقة.

تطبيق 4

في التطبيقات السابقة لم نتمكن من عرض الـ Fragment الآخر في حالة الهاتف وذلك لأن التبديل بين Fragment وآخر لا تتم إلا داخل النشاط المضيف، لذا ينبغي أن نجعل الشيفرة الخاصة بالـ Fragment قادرة على التحدث مع النشاط المضيف لها وتنفيذ شيفرات بداخله. ويمكننا القيام بذلك باستخدام interface كحلقة وصل بين النشاط المضيف والـ Fragment.

نضع أولًا زر في الواجهة الخاصة بـ fragment_example.xml عند الضغط عليه ننتقل إلى الـ fragment_details كالآتي.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:background="#ccefff"
	android:orientation="vertical"
	android:gravity="center">

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Hello From Example Fragment"/>

	<Button
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Go to Details Frafment"
		android:id="@+id/go_to_btn"/>

</LinearLayout>

ثم ننتقل إلى الشيفرة الخاصة بـ ExampleFragment.java وبداخلها نقوم بصنع interface جديد يُدعى OnBtnClicked وبداخله التابع ()goToFragment.

public interface OnBtnClicked{
	public void goToFragment();
}

ثم نجعل الصنف الخاص بالنشاط المضيف يقوم باستخدام هذا الـ interface.

public class MainActivity extends Activity implements ExampleFragment.OnBtnClicked

نعود مجددًا للشيفرة الخاصة بـ ExampleFragmen.java ونقوم بكتابة التابع ()onAttach الخاص بدورة الحياة للـ Fragment كالتالي:

private MainActivity mContext;

@Override
public void onAttach(Context context) {
	super.onAttach(context);
	mContext = (MainActivity) context;
}

ووظيفة الشيفرة السابقة جعل الشيفرة الخاصة بالـ Fragment تستطيع التواصل مع النشاط المضيف باستخدام كائن منه يُمرر لحظة ربط الـ fragment بالنشاط المضيف.

ثم بعد ذلك لربط الزر الذي قمنا بإضافته في الواجهة بالشيفرة نستخدم التابع ()findViewById ولكن هذه المرة باستخدام الكائن rootView وذلك لأنه يُعبر عن الواجهة فلا يمكننا من استدعاء التابع مباشرة كما كان داخل النشاط.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

	View rootView =inflater.inflate(R.layout.fragment_example, container, false);

	Button btn = (Button)rootView.findViewById(R.id.go_to_btn);

	btn.setOnClickListener(new View.OnClickListener() {
		@Override
		public void onClick(View v) {
			mContext.goToFragment();
		}
	});

	return rootView;
}

ونجعل الزر مستعدًا للاستجابة عند الضغط عليه على أن يقوم باستدعاء التابع ()goToFragment باستخدام الكائن الخاص بالنشاط المضيف. لتصبح الشيفرة النهائية لهذا الـ Fragment كالتالي:

package apps.noby.simplefragment;
import android.content.Context;
import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;


public class ExampleFragment extends Fragment {

	private MainActivity mContext;
  
	@Override
	public void onAttach(Context context) {
		super.onAttach(context);
		mContext = (MainActivity) context;
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

		View rootView =inflater.inflate(R.layout.fragment_example, container, false);

		Button btn = (Button)rootView.findViewById(R.id.go_to_btn);

		btn.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				mContext.goToFragment();
			}
		});
		return rootView;
	}

	public interface OnBtnClicked{

		public void goToFragment();
	}
}

يتبقى فقط إضافة الوظيفة التي نريدها عن الضغط على هذا الزر وذلك بكتابة الشيفرة الخاصة بالتابع ()goToFragment داخل النشاط المضيف.

package apps.noby.simplefragment;

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends Activity implements ExampleFragment.OnBtnClicked {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		if(findViewById(R.id.view_container) != null){
			FragmentManager fragmentManager = getFragmentManager();
			FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
			ExampleFragment frag = new ExampleFragment();
			fragmentTransaction.add(R.id.view_container, frag);
			fragmentTransaction.commit();
		}
	}

	@Override
	public void goToFragment() {
		if(findViewById(R.id.view_container) != null){
			FragmentManager fragmentManager = getFragmentManager();
			FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
			DetailsFragment frag = new DetailsFragment();
			fragmentTransaction.replace(R.id.view_container, frag);
			fragmentTransaction.addToBackStack(null);
			fragmentTransaction.commit();
		}
	}
}

وتتشابه الشيفرة داخل التابع ()goToFragment مع الأخرى المتواجدة داخل ()onCreate ولكننا نستخدم التابع ()replace بدلًا من ()add وذلك لتبديل الواجهات داخل نفس العنصر FrameLayout بشكل ديناميكي، كما نستدعي التابع ()addToBackStack وذلك حتى نضيف واجهة الـ Fragment السابق في الذاكرة الخاصة بزر الرجوع حتى نعود إليها عند الضغط على زر الرجوع وإلا سيتم غلق التطبيق.

وتوضح الصورة التالية ما المقصود بذلك:

12.png

والآن عند تجربة التطبيق على المحاكي (الهاتف أو الحاسب اللوحي) نجد أننا نستطيع الوصول إلى الـ DetailsFragment عند الضغط على الزر.

تطبيق 5

في التطبيق التالي سنقوم بإرسال نص من Fragment إلى آخر للتحدث فيما بينهما.

13.png

14.png

ويتم ذلك عن طريق استخدام التابع ()setArguments وللقيام بذلك نقوم بتلك التعديلات على التطبيق السابق.

في ملف الواجهة fragment_example.xml نضيف عنصر EditText بالخصائص الآتية.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:background="#ccefff"
	android:orientation="vertical"
	android:gravity="center">

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Hello From Example Fragment"/>

	<EditText
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:hint="Enter Your Name"
		android:id="@+id/edit_txt"/>

	<Button
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Go to Details Frafment"
		android:id="@+id/go_to_btn"/>

</LinearLayout>

وفي ملف الواجهة fragment_details نضيف الخاصية id للعنصر TextView.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:background="#ffc829"
	android:orientation="vertical">

	<TextView
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:text="Hello From Details Fragment"
		android:gravity="center"
		android:id="@+id/txt_view"/>

</LinearLayout>

وفي ملف الواجهة الرئيسي الخاص بالشاشات ذات الحجم الكبير نقوم بتغيير العنصر fragment الثاني بالعنصر FrameLayout.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical">

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Hello Main Activity!"
		android:layout_gravity="center"
		android:textSize="25sp"/>

	<LinearLayout
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:orientation="horizontal">
		
		<fragment
			android:layout_width="0dp"
			android:layout_height="match_parent"
			android:layout_weight="1"
			android:name="apps.noby.simplefragment.ExampleFragment"
			android:id="@+id/fragment_container"
			tools:layout="@layout/fragment_example" />

		<FrameLayout
			android:layout_width="0dp"
			android:layout_height="match_parent"
			android:layout_weight="1"
			android:id="@+id/fragment_details"/>
	</LinearLayout>
</LinearLayout>

داخل الشيفرة الخاصة بـ ExampleFragment.java نقوم بتغيير التابع داخل الـ interface ليقبل تمرير نص، ثم عند الضغط على الزر نرسل النص المكتوب داخل عنصر الواجهة EditText إلى التابع ()goToFragment.

package apps.noby.simplefragment;
import android.content.Context;
import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;


public class ExampleFragment extends Fragment {

	private MainActivity mContext;
  
	@Override
	public void onAttach(Context context) {
		super.onAttach(context);
		mContext = (MainActivity) context;
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

		final View rootView =inflater.inflate(R.layout.fragment_example, container, false);

		Button btn = (Button)rootView.findViewById(R.id.go_to_btn);

		btn.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				EditText et = (EditText) rootView.findViewById(R.id.edit_txt);
				String name = et.getText().toString();
				mContext.goToFragment(name);
			}
		});

		return rootView;
	}

	public interface OnBtnClicked{

		public void goToFragment(String name);
	}
}

داخل شيفرة النشاط الرئيسي MainActivity.java سنقوم بإرسال النص المرسل مع الكائن DetailsFragment باستخدام التابع ()setArguments.

package apps.noby.simplefragment;

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends Activity implements ExampleFragment.OnBtnClicked {

	public static final String ARGUMENT_NAME = "name";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		if(findViewById(R.id.view_container) != null){
			FragmentManager fragmentManager = getFragmentManager();
			FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
			ExampleFragment frag = new ExampleFragment();
			fragmentTransaction.add(R.id.view_container, frag);
			fragmentTransaction.commit();
        }
	}

	@Override
	public void goToFragment(String name) {
		FragmentManager fragmentManager = getFragmentManager();
		FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
		DetailsFragment frag = new DetailsFragment();
		Bundle bundle = new Bundle();
		bundle.putString(ARGUMENT_NAME,name);
		frag.setArguments(bundle);

		if(findViewById(R.id.view_container) != null){
			fragmentTransaction.replace(R.id.view_container, frag);
			fragmentTransaction.addToBackStack(null);
			fragmentTransaction.commit();
		}
		else{
			fragmentTransaction.replace(R.id.fragment_details, frag);
			fragmentTransaction.commit();
		}
	}
}

وتم إضافة حالتين الأولى للواجهات الصغير والأخرى عن التعامل مع الواجهات الكبيرة.

أخيرًا نقوم باستقبال النص في الشيفرة الخاصة بـ DetailsFragment.java وعرضه بداخل العنصر TextView.

package apps.noby.simplefragment;

import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class DetailsFragment extends Fragment {

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

		View rootView = inflater.inflate(R.layout.fragment_details,container,false);

		TextView tv = (TextView) rootView.findViewById(R.id.txt_view);
		Bundle bundle = getArguments();
		String str = bundle.getString(MainActivity.ARGUMENT_NAME);
		tv.setText("Hello " + str + " From Details Fragment");
		return rootView;
	}
}

بعد ذلك نقوم بتشغيل التطبيق على المحاكي وتجربته للتأكد من أداءه للوظيفة المطلوبة بشكل سليم.

15.png

بهذا نكون قد وصلنا إلى نهاية هذا الدرس، في انتظار تجربتكم وآرائكم.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن