اذهب إلى المحتوى

العناصر الأساسية لبناء تطبيقات أندرويد والتحكم بواجهة المستخدم


أحمد النوبي

العناصر الأساسية في تطبيقات أندرويد

android-activities.png

تتكون تطبيقات أندرويد من أربعة عناصر أساسية لكل منها دورها الخاص الذي تستطيع القيام به، ويتم استخدامها بناءً على الغرض أو المهمة المطلوب تنفيذها وهي:

النشاطات Activities

يُمثل واجهة واحدة والتي تظهر أمام المستخدم ويتفاعل معها، وقد يتكون التطبيق من عدة واجهات مختلفة وبالتالي عدة Activities مختلفة، فمثلًا تطبيق البريد الإلكتروني لديه واجهة خاصة بعرض البريد الجديد وعند قراءة محتوى رسالة محددة تظهر لك في واجهة أخرى مخصصة لذلك وإذا أردت إرسال رسالة بريد جديدة يظهر لك واجهة جديدة لقيام بهذه المهمة.

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

الخدمات Services

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

مستقبلات البث Broadcast Receivers

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

لذا فهذا العنصر هو المسؤول عن استقبال هذا النوع من الرسائل واتخاذ القرار المناسب.

مزودو المحتوى Content Providers

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

النشاطات Activities

نبدأ أولًا بالنشاطات Activities حيث لا يخلو تطبيق من واجهة للمستخدم، لصنع نشاط في التطبيق يتم ذلك عن طريق كتابة صنف جديد Class يرث من صنف آخر اسمه Activity، وهذا الصنف هو الأب دائمًا عند عمل نشاط جديدة ويحتوي على التوابع Methods الخاصة بالنشاط والمكوّنة لها.

public class MainActivity extends Activity {

}

إن كانت لديك خبرة سابقة في التعامل مع أي لغة برمجة فمن المؤكد أنك رأيت الدالة ()main والتي تُمثل نقطة البداية للبرنامج، ولكن في أندرويد هناك نشاط رئيسي يُمثل نقطة البداية للتطبيق ولكل نشاط دورة حياة خاصة به، وتتكون من مجموعة من التوابع methods يتم استدعاؤها لحظة إنشاء النّشاط ومجموعة من التوابع التي يتم استدعاؤها لحظة إزالة النّشاط ونجدها في الصورة التالية.

Activity-Life-Cycle (1).png

دورة حياة النشاط

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

مثال 1

الآن قم بفتح Android Studio وسنقوم بعمل تطبيق جديد اسمه "Activity Life Cycle" حتى نرى كيف ينادي النظام التّوابع الخاصة بالنّشاط.

سنختار Empty Activity وندع باقي الاختيارات كما هي.

1.png

بعد الانتهاء ستجد في المجلد java صنف Class يدعى MainActivity ويرث من Activity (قد تجد الصنف يرث من AppCompatActivity يمكنك تغييرها إلى Activity أو دعها كما هي فلا يوجد حاليًا فرق بينها).

ستجد داخل الصنف التابع ()onCreate.

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

وستجد بداخلها أنها تستدعي ;(setContentView(R.layout.activity_main وتمرر لها R.layout.activity_main، والحرف R يعني المجلد res، و Layout هو المجلد المتواجد داخل res بنفس الاسم، وبداخله نجد الملف activity_main.xml.

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

سنضع هذا السطر بداخل ()onCreate.

Toast.makeText(this,"onCreate Method",Toast.LENGTH_SHORT).show();

ومن خلال هذا السطر نقوم بإظهار رسالة للمستخدم لمدة محددة كما في الصورة.

2.png

والآن نقوم بنفس الشيء لباقي التوابع:

package apps.noby.activitylifecycle;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toast.makeText(this,"onCreate Method",Toast.LENGTH_SHORT).show();
  }

  @Override
  protected void onStart() {
    super.onStart();
    Toast.makeText(this,"onStart Method",Toast.LENGTH_SHORT).show();
  }

  @Override
  protected void onResume() {
    super.onResume();
    Toast.makeText(this,"onResume Method",Toast.LENGTH_SHORT).show();
  }

  @Override
  protected void onStop() {
    super.onStop();
    Toast.makeText(this,"onStop Method",Toast.LENGTH_SHORT).show();
  }

  @Override
  protected void onPause() {
    super.onPause();
    Toast.makeText(this,"onPause Method",Toast.LENGTH_SHORT).show();
  }  

  @Override
  protected void onDestroy() {
    super.onDestroy();
    Toast.makeText(this,"onDestroy Method",Toast.LENGTH_SHORT).show();
  }

  @Override
  protected void onRestart() {
    super.onRestart();
    Toast.makeText(this,"onRestart Method",Toast.LENGTH_SHORT).show();
  }
}

ثم نقوم بتشغيله على المحاكي لنرى التطبيق وهو يعمل.

ملاحظات

  1. التابع ()onCreate هو التابع الوحيد الذي نقوم بداخله بتعريف واجهة المستخدم من داخل المجلد layout باستخدام التابع ()setContentView.
  2. لا يشترط عند كتابة التوابع كتابتها بترتيب استدعائها.
  3. يجب أن يحتوي النّشاط على التابع ()onCreate وإلا لن تعمل، وباقي التوابع نستخدم منها فقط ما نحتاجه لأداء مهمة محددة في وقت محدد، فمثلًا إذا كان التطبيق يستخدم الكاميرا وقام المستخدم بغلق التطبيق أو الانتقال إلى تطبيق أخر فيجب عند هذه الحالة قبل اختفاء النّشاط غلق الكاميرا حتى لا يتم إهدار الموارد دون استخدام، فأنسب مكان لذلك هو التابع ()onStop فهو الذي يُعبر عن هذه الحالة .
  4. عند صنع نشاط جديد يجب تعريفه في ملف AndroidManifest.xml كما بالشكل، وإذا لم يتم تعريفه فلن تعمل ولن يستطيع نظام التشغيل تشغيلها.
...
<application
  android:allowBackup="true"
  android:icon="@mipmap/ic_launcher"
  android:label="@string/app_name"
  android:supportsRtl="true"
  android:theme="@style/AppTheme">

  <activity android:name=".MainActivity">
    <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
  </activity>
  
</application>
...

يُستخدم ملف AndroidManifest.xml لتعريف نظام التشغيل بالمكونات الأساسية التي يتكون منها التطبيق، داخل الوسم <application> سنجد وسمًا آخر يدعى <activity> وهو الوسم المستخدم لتعريف نظام التشغيل أن التطبيق يحتوي على Activity ولدى هذا الوسم الخاصية name لمعرفة ملف جافا الخاص بهذا الـ Activity.

وكما ذكرنا أن تطبيقات أندرويد تتكون من Activity رئيسية تمثل نقطة البداية للتطبيق لذا لتعريف النظام بأن هذا النّشاط هو الرئيسي نستخدم الوسم <intent-filter> وبداخله يتم تعريف Action من النوع MAIN وتعريف Category من النوع LAUNCHER وإذا لم يتم تعريفهما معًا فلن تظهر الأيقونة الخاصة بالتطبيق مع باقي التطبيقات في قائمة التطبيقات الرئيسية.

مثال 2

سنقوم بصنع تطبيق كما في الصورة التالية يحتوي على نص وزر عند الضغط عليه يتغير النص إلى كلمة أخرى من اختيارنا.

3.png

أولًا نقوم بصنع الواجهة الخاصة بالتطبيق في ملف activity_main.xml:

<?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 World!”
    android:id=”@+id/txt”/>

  <Button
    android:layout_width=”wrap_content”
    android:layout_height=”wrap_content”
    android:text=”Change Textandroid:id=”@+id/chngbtn”/>

</LinearLayout>

لا يوجد اختلاف بينه وبين ما قمنا بعمله سابقًا، واستخدمنا خاصية id للتحكم في هذا العنصر من شيفرة الجافا وأعطينا لكل عنصر Id مميز.

ثانيًا الاستجابة للزر برمجيًا وتغيير النص، ولكن يجب أولًا أن نصنع متغير ونربطه بالزر المصنوع في xml، ونقوم بذلك كما في الشكل التالي:

Button btn;
...
btn = (Button) findViewById(R.Id.chngbtn);

قمنا بتعريف متغير من الصنف Button وهو صنف موجود في المكتبات الخاصة بأندرويد، وقمنا بربطه باستخدام التابع ()findViewById وتمرير له R.Id.chngbtn وتعني من المجلد res ستجد عنصر له Id قيمته chngbtn.

ويقوم هذا التابع بالبحث عن العناصر بمعرفة Id الخاص بها وإرجاعها، وقمنا باستخدام خاصية التحويل Casting وذلك لأن التابع يرجع عنصرًا من النوع View وهو الأب للعنصر Button لذا نقوم بتحويله إلى الصنف المحدد عن طريق (Button). وبالمثل نقوم بربط TextView في شيفرة الجافا.

والآن لجعل الزر يفعل شيئًا عند الضغط عليه نقوم باستخدام Listeners والتي تقوم بدورها بانتظار أن يضغط المستخدم على الزر حتى تقوم بوظيفة ما.

btn.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
    //Do Something here
  }
});

باستخدام المتغير btn نقوم باستدعاء التابع setOnClickListener وتمرير له ()new View.OnClickListener وهو ما يدعى بالصنف المجهول Anonymous class، وبداخله نجد الدالة onClick والتي تُستدعى عند الضغط على الزر ويتم تنفيذ الأوامر التي بداخلها.

سنقوم الآن بتغيير الكلمة عند الضغط على الزر من !Hello World إلى Hello Android Developer فداخل onClick نكتب:

String str = "Hello Android Developer";
tv.setText(str);

قمنا بتعريف متغير من النوع String وتخزين النص بداخله، وباستخدام المتغير tv نستدعي التابع setText وهو المسؤول عن كتابة نص في TextView برمجيًا ومساوٍ للخاصية android:text في xml ونمرر له النص المراد كتابته.

لتُصبح الشيفرة النهائية هي:

package apps.noby.controlapps;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


public class MainActivity extends Activity {

  Button btn;
  TextView tv;

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

    btn =(Button) findViewById(R.id.chngbtn);
    tv = (TextView) findViewById(R.id.txt);

    btn.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        String str = "Hello Android Developer";
        tv.setText(str);
      }
    });
  }
}

عند تشغيل التطبيق على المحاكي والضغط على الزر نجد أن التطبيق يعمل بالشكل المطلوب.

4.png

ملاحظات

  1. يتم تعريف نوع المتغيرات خارج التابع onCreate.
  2. تستخدم import عندما يكون هناك صنف متواجد في إحدى المكتبات ونريد تضمينه داخل تطبيقنا فنقوم بكتابة بجانب الأمر import ومكان تواجد هذا الصنف.
  3. يجب ذكر اسم الحزمة في بداية الشيفرة الخاصة بالنّشاط.
  4. يمكن تمرير النص للتابع setText في سطر واحد مثل:
tv.setText("Hello Android Developer");

بدلًا من تخزين قيمة النص في متغير ثم تمرير هذا المتغير.

مثال 3

سنضيف في هذا المثال EditText ليستطيع المستخدم كتابة نص ثم سنقوم بعرضه له بدلًا من عرض نص محدد مسبقًا.

5.png

أولًا نبدأ بصنع الواجهة ولن تختلف عن واجهة المثال السابق إلا بزيادة عنصر من النوع EditText:

<?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 World!"
    android:id="@+id/txt"/>

  <EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Write your Name here"
    android:id="@+id/edt"/>

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Change Text"
    android:id="@+id/chngbtn"/>

</LinearLayout>

الآن للتحكم فيما يكتبه المستخدم بداخل EditText برمجيًا نعرف متغيرًا جديدًا داخل ملف MainActivity.java ونربطه بالعنصر الذي قمنا بتعريف في الواجهة. ولتحديد ما سيتم كتابته عند الضغط على الزر:

String str = et.getText().toString();
tv.setText("Hello " + str + "!");

باستخدام المتغير get نستدعي التابع getText وهو المسؤول عن إعادة البيانات التي قام المستخدم بكتابتها في العنصر EditText ثم نستدعي التابع toString لتحويلها إلى نص و تخزينه بداخل المتغير str ، ثم نمرر هذا المتغير إلى التابع setText كما سبق، تستخدم "+" للربط بين نصين ففي هذا المثال نريد الربط بين كلمة Hello والقيمة التي أدخلها المستخدم.

package apps.noby.controlapps;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity {

  Button btn;
  TextView tv;
  EditText et;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    btn = (Button) findViewById(R.id.chngbtn);
    tv = (TextView) findViewById(R.id.txt);
    et = (EditText) findViewById(R.id.edt);

    btn.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        String str = et.getText().toString();
        tv.setText("Hello " + str + "!");
      }
    });
  }
}

ثم نقوم بتجربة التطبيق على المحاكي.

6.png

مثال 4

سنقوم بصنع تطبيق يظهر صورة عند الضغط على الزر ثم يخفيها عند الضغط عليه مرة أخرى.

7.png

أولًا نبدأ بصنع واجهة المستخدم:

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

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Show"
    android:onClick="showImage"
    android:id="@+id/show"/>

  <ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/and_img"
    android:visibility="invisible"
    android:id="@+id/ic"/>

</LinearLayout>

قمنا بتغيير أماكن ظهور العناصر إلى المنتصف عن طريق الخاصية:

android:gravity="center"

وجعلنا الصورة عند بداية استخدام التطبيق غير ظاهرة باستخدام الخاصية:

android:visibility="invisible"

واستخدمنا خاصية جديدة للزر تدعى:

android:onClick="showImage"

وتستخدم كطريقة أخرى لصنع Listener ينتظر ضغط المستخدم على الزر ولكن عن طريق xml.

ثانيًا للتحكم بهذه الواجهة نقوم في ملف MainActivity.java بربط ImageView بمتغير كما سبق.

ولظهور واختفاء الصورة عند الضغط على الزر:

public void showImage(View view){
  if(img.getVisibility() == View.VISIBLE){
    img.setVisibility(View.INVISIBLE);
    showBtn.setText("Show");
  }
  else{
    img.setVisibility(View.VISIBLE);
    showBtn.setText("Hide");
  }
}

نستخدم هذه الدالة والتي يجب أن تكون كالتالي:

  1. الوصول لها Public ولا ترجع أية قيمة (يعني تُرجع void)
  2. الاسم الخاص بها نفس الاسم الذي كتبناه سابقًا للخاصية onClick.
  3. تمرير للدالة عنصر من النوع View.

ثم نقوم بكتابة ما سيحدث عند الضغط على الزر كما كنا نفعل سابقًا.

ونقوم داخل الدالة بالتحقق إذا ما كان العنصر ظاهرًا باستدعاء التابع ()getVisibility باستخدام المتغير img، فإن كان ظاهرًا نقوم بإخفائه باستدعاء التابع ()setVisibility وتمرير له الثابت View.INVISIBLE، ثم نغير النص المكتوب على الزر.

أما إذا كانت الصورة غير ظاهرة نقوم بتمرير الثابت View.VISIBLE للتابع ()setVisibility، ثم تغيير النص المكتوب على الزر أيضًا.

package apps.noby.controlapps;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends Activity {

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

    showBtn = (Button) findViewById(R.id.show);
    img =(ImageView) findViewById(R.id.ic);
  }

  public void showImage(View view){
    if(img.getVisibility() == View.VISIBLE){
      img.setVisibility(View.INVISIBLE);
      showBtn.setText("Show");
    }
    else{
      img.setVisibility(View.VISIBLE);
      showBtn.setText("Hide");
    }
  }
}

ثم نقوم بتجربة التطبيق على المحاكي.

8.png

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


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

أفضل التعليقات



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...