المحتوى عن 'واجهة المستخدم'.



مزيد من الخيارات

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المُحتوى


التصنيفات

  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • نصائح وإرشادات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • مقالات عامة

التصنيفات

  • PHP
    • Laravel
    • ووردبريس
    • Magento
  • جافاسكريبت
    • Node.js
    • jQuery
    • AngularJS
  • HTML5
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • سي شارب #C
    • منصة Xamarin
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • برمجة أندرويد
  • لغة R
  • سير العمل
    • Git
  • صناعة الألعاب
    • Unity3D
  • مقالات عامّة

التصنيفات

  • تجربة المستخدم
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
    • كوريل درو
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
  • مقالات عامّة

التصنيفات

  • خواديم
    • الويب HTTP
    • قواعد البيانات
    • البريد الإلكتروني
    • DNS
    • Samba
  • الحوسبة السّحابية
    • Docker
  • إدارة الإعدادات والنّشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات

التصنيفات

  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • مقالات عامة

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
  • أندرويد
  • iOS
  • macOS
  • ويندوز

التصنيفات

  • شهادات سيسكو
    • CCNA
  • شهادات مايكروسوفت
  • شهادات Amazon Web Services
  • شهادات ريدهات
    • RHCSA
  • شهادات CompTIA
  • مقالات عامة

أسئلة وأجوبة

  • الأقسام
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة البرمجة
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات
    • أسئلة الشهادات المتخصصة

التصنيفات

  • ريادة الأعمال
  • العمل الحر
  • التسويق والمبيعات
  • البرمجة
  • التصميم
  • DevOps

تمّ العثور على 12 نتائج

  1. لدى برمجة تطبيقات أندرويد، فإنّه وفي العديد من الأحيان نحتاج أن نعرض مجموعة من العناصر معًا في النشاط أمام المستخدم كعرض جهات الاتصال مثلًا أو الرسائل حيث يمكن للمستخدم أن يتصفحها ويتنقل بينها سريعًا، كما يمكنه الضغط على أي منها فيتم عرض المزيد من المعلومات عن هذا العنصر. نستخدم عنصر الواجهة ListView أو قائمة العرض للقيام بما سبق والذي يتيح عرض أكثر من عنصر في قائمة واحدة، ويتم إضافة العناصر إلى قائمة العرض تلقائيًا باستخدام كائن من الصنف Adapter والذي يستطيع جلب المحتوى من مصفوفة من البيانات أو قاعدة بيانات. ويُعتبر الصنف Adapter بمثابة حلقة الوصل بين عنصر الواجهة الخاص بقائمة العرض ومصدر البيانات المستخدم حيث يقوم بتشكيلها بالشكل المطلوب لتُمثل عنصرًا من عناصر القائمة. يوجد عدة أصناف فرعية من الصنف Adapter كي تُستخدم مع أشكال البيانات المختلفة مثل: ArrayAdapter Base Adapter SimpleCursorAdapter قوائم العرض مع كائن من ArrayAdapter تطبيق 1 يُستخدم ArrayAdapter عندما يكون المصدر البيانات هو قائمة أو مصفوفة. سنقوم بصنع تطبيق يعرض مجموعة من النصوص في قائمة العرض، لذا سنقوم بعمل مشروع جديد في Android Studio. ولتكوين قائمة العرض نقوم بالخطوات التالية: نضع عنصر من ListView في ملفات الواجهة ونشير له بـ Id مميز، ثم نربط هذا العنصر بشيفرات الجافا باستخدام التابع ()findViewById. <?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"> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"/> </LinearLayout> نصنع ملف واجهة جديد يكون الجذر له عنصر من النوع TextView. ولصنع واجهة استخدام جديدة نضغط على المجلد layout المتواجد داخل res بالزر الأيمن ثم نختار: new > XML > Layout XML File ونسميه row_item. وبداخل هذا الملف نجعل عنصر الجذر من النوع TextView ونغير حجم النص الذي سيُكتب بداخله إلى 20sp ونجعل الخاصية padding لها قيمة 20sp. لاحظ أن الخاصية padding تعني الحشو أي ضع فراغًا مقداره 20sp حول النص المكتوب داخل TextView في كل الاتجاهات. <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="20sp" android:padding="20sp"/> في شيفرة الجافا نبني كائنًا جديدًا من الصنف ArrayAdapter ونمرر لدالة البناء الخاصة به ثلاث عناصر: العنصر الأول هو this والذي يعبر عن السياق الخاص بالنشاط المكوّن للقائمة. العنصر الثاني هو TextView الذي سيُعرض فيه كل نص في القائمة. العنصر الأخير هو المصفوفة الخاصة بالنصوص التي سيتم عرضها في قائمة العرض. ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsArray); نستدعي التابع ()setAdapter باستخدام الكائن الخاص بالـ ListView ونمرر له الـ Adapter الذي قمنا بصنعه ليصبح التطبيق النهائي: package apps.noby.listviewexample; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { private ListView lv; String [] colorsArray = {"Red","Yellow","Blue","Orange","Black","Green","Brown","Grey"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lv =(ListView) findViewById(R.id.listview); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsArray); lv.setAdapter(adapter); } } ثم نقوم بتجربة التطبيق على المحاكي لنتأكد من عمله بالشكل المطلوب. تطبيق 2 في هذا التطبيق بدلًا من استخدام المصفوفة لتخزين البيانات سنقوم باستبدالها بقائمة من النوع ArrayList مع الحفاظ على باقي ملفات الواجهة السابقة لتصبح الشفرة النهائية بعد التعديل: package apps.noby.listviewexample; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView lv; ArrayList<String> colorsList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); colorsList = new ArrayList<String>(); colorsList.add("Red"); colorsList.add("Yellow"); colorsList.add("Blue"); colorsList.add("Orange"); colorsList.add("Black"); colorsList.add("Green"); colorsList.add("Brown"); colorsList.add("Grey"); lv =(ListView) findViewById(R.id.listview); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsList); lv.setAdapter(adapter); } } وبتجربة هذا التطبيق على المحاكي نجد أنه يقوم بنفس الوظيفة السابقة، ولكن استخدامنا للقائمة بدلًا من المصفوفة يتيح لنا القدرة على إضافة أو إزالة العناصر بسهولة وهو ما لا تسمح به المصفوفة. استجابة عناصر القائمة عند الضغط عليها تطبيق 3 سنقوم في هذا التطبيق بجعل العناصر المتواجدة بقائمة العرض تستجيب عند الضغط عليها، فكما ذكرنا سابقًا أننا استخدمنا كائنًا من نوع ArrayList لسهولة إضافة أو إزالة العناصر منه، سنقوم الآن بتطبيق هذا المفهوم حيث سنقوم بإزالة العنصر من القائمة عند الضغط عليه. كما نستخدم التابع ()setOnClickListener مع الزر للاستجابة عند الضغط عليه سنقوم هنا باستخدام التابع ()setOnItemClickListener للاستجابة عند الضغط على عناصر القائمة، وبداخله نجد الدالة ()onItemClick وبها العناصر التالية: parent وهو يُعبر عن الأب الخاص بالعنصر الذي تم الضغط عليه وفي هذا المثال فهو ListView. view ويُمثل العنصر نفسه الذي تم الضغط عليه أي TextView. position ويُعبر عن رقم العنصر في القائمة. Id وهو رقم مميز يتم تحديده لعنصر الواجهة الذي يعرض النص. لتصبح الشفرة النهائية الخاصة بهذا التطبيق: package apps.noby.listviewexample; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView lv; ArrayList<String> colorsList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); colorsList = new ArrayList<String>(); colorsList.add("Red"); colorsList.add("Yellow"); colorsList.add("Blue"); colorsList.add("Orange"); colorsList.add("Black"); colorsList.add("Green"); colorsList.add("Brown"); colorsList.add("Grey"); lv =(ListView) findViewById(R.id.listview); final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsList); lv.setAdapter(adapter); lv.setOnItemClickListener(new ListView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String item = (String) parent.getItemAtPosition(position); colorsList.remove(item); adapter.notifyDataSetChanged(); } }); } } بداخل التابع ()onItemClick نستخدم الكائن parent لاستدعاء التابع ()getItemAtPosition وتمرير رقم العنصر له حتى يُعيد لنا النص المخزن في ذلك العنصر. ثم بعد ذلك نقوم بإزالة العنصر من القائمة واستدعاء التابع ()notifyDataSetChanged باستخدام الكائن adapter وذلك لتنفيذ التغيير الذي حدث على عناصر قائمة العرض. الآن نقوم بتجربة التطبيق على المحاكي، نجد أنه عند الضغط على أحد العناصر تختفي في الحال. يمكنك تطوير هذا التطبيق لجعله يستدعي نشاطًا جديدًا عند الضغط على أحد العناصر. البحث داخل عناصر قائمة العرض تطبيق 4 في هذا التطبيق سنقوم بالبحث عن العناصر التي تحتوي على نص معين وعرضها هي فقط وذلك لسهولة الوصول لعنصر محدد. كتطوير على التطبيق السابق سنقوم بإضافة الخاصية Orientation للعنصر LinearLayout في ملف activity_main.xml. android:orientation="vertical" ثم نضيف عنصر EditText قبل ListView وهو العنصر الذي سيقوم المستخدم بالتفاعل معه وكتابة النص الذي سيبحث عنه داخل قائمة العرض. <?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"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_search_black_24dp" android:hint="Search..." android:id="@+id/searchedittext"/> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"/> </LinearLayout> ولإضافة صورة يسار العنصر EditText نستخدم الخاصية drawableLeft ثم نضيف الصورة من المجلد drawable. الآن سنربط هذا العنصر بالشيفرة الرئيسية للتطبيق ثم نستدعي التابع ()addTextChangedListener والذي يستجيب لأي تغير يحدث داخل EditText، حيث يوفر ثلاث دوال: searchET.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { adapter.getFilter().filter(s); } @Override public void afterTextChanged(Editable s) { } }); كل دالة تختص بفترة محددة، وهنا نهتم بالدالة ()onTextChanged والتي يتم استدعاؤها لحظة تغير النص المكتوب بداخل EditText. أخيرًا لتنقية القائمة وترك فقط العناصر التي يتم البحث عنها نستدعي التابع ()getFilter باستخدام الكائن الخاص بالـ ArrayAdapter ثم نستدعي بعده مباشرة التابع ()filters ونمرر له المتغير s الذي يحمل بداخله النص الذي قمنا بكتابته في EditText. لتصبح الشيفرة النهائية كما يلي: package apps.noby.listviewexample; import android.app.Activity; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView lv; private ArrayList<String> colorsList; EditText searchET; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); colorsList = new ArrayList<String>(); colorsList.add("Red"); colorsList.add("Yellow"); colorsList.add("Blue"); colorsList.add("Orange"); colorsList.add("Black"); colorsList.add("Green"); colorsList.add("Brown"); colorsList.add("Grey"); searchET = (EditText) findViewById(R.id.searchedittext); lv =(ListView) findViewById(R.id.listview); final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsList); lv.setAdapter(adapter); lv.setOnItemClickListener(new ListView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String item = (String) parent.getItemAtPosition(position); colorsList.remove(item); adapter.notifyDataSetChanged(); } }); searchET.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { adapter.getFilter().filter(s); } @Override public void afterTextChanged(Editable s) { } }); } } بعد ذلك نقوم بتجربة التطبيق على المحاكي لنتأكد من عمله كما ينبغي. تخصيص واجهة قائمة العرض تطبيق 5 تُتيح قائمة العرض ميزة تخصيص العناصر، فيمكن صنع قائمة عرض تتكون من نص وصورة في كل صف بدلًا من نص فقط كالأمثلة السابقة. في هذا التطبيق سنقوم بصنع تطبيق يعرض قائمة كما في الصورة التالية، وعند الضغط على العنصر يعرض نشاطًا جديدًا. من Android Studio نقوم بعمل مشروع جديد، نبدأ أولًا بصنع واجهة المستخدم الرئيسية والمتكونة من ListView. <?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"> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"/> </LinearLayout> نصنع ملف واجهة جديد يُعبر عن الصف ويدعى row_item وبداخله نكون الشكل المطلوب للصف في قائمة العرض كما في المخطط التالي: <?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="horizontal"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/blue" android:id="@+id/imgview"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Title Example" android:textSize="20sp" android:textStyle="bold" android:id="@+id/titleview"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Description Example" android:textSize="15sp" android:id="@+id/descview"/> </LinearLayout> </LinearLayout> يتكون الصف في قائمة العرض من صورة، نص العنوان ونص الوصف لذا سنصنع صنفًا جديدًا يحتوي بداخله على هذه المعلومات الخاصة بالعنصر ثم نكوّن مصفوفة أو قائمة من هذه الصنف. لصنع صنفًا جديدًا من داخل المجلد java اضغط بالزر الأيمن على اسم الحزمة الخاصة بالمشروع واختر New > Java Class ثم اكتب اسم الصنف الجديد ListItem. بداخل هذا الصنف الجديد نعرّف ثلاث متغيرات، package apps.noby.customlistviewexample; public class ListItem { public int imgSrc; public String title; public String desc; } في ملف MainActivity.java نكتب الشيفرة الخاصة بالمشروع. أولًا نصنع كائنًا من الصنف ArrayList ليكون هو مصدر البيانات التي ستُعرض في قائمة العرض، ويتكون الكائن من مجموعة من الكائنات الأخرى من الصنف ListItem لكل منها صورة خاصة به، نص العنوان ونص الوصف. private ArrayList<ListItem> items; String [] colorsArray = {"Red","Yellow","Blue","Black","Green","Brown","Grey"}; int [] colorsImage = {R.drawable.red,R.drawable.yellow,R.drawable.blue,R.drawable.black,R.drawable.green,R.drawable.brown,R.drawable.grey}; items = new ArrayList<ListItem>(); for(int i=0;i<colorsArray.length;i++){ ListItem item= new ListItem(); item.imgSrc=colorsImage; item.title = colorsArray; item.desc = colorsArray + " Color!"; items.add(item); } ثانيًا بعد ذلك نصنع الكائن الخاص بالـ ArrayAdapter ولكننا لا يمكننا استخدام الصنف الأساسي من ArrayAdapter حيث أنه لا يعمل إلا مع نص واحد فقط ولن يمكننا تغيير الصورة أو النص الثاني، لذا ينبغي علينا صنع الصنف ArrayAdapter الخاص بنا. بنفس الطريقة السابقة نصنع صنف جديد من داخل المجلد Java، اضغط بالزر الأيمن على اسم الحزمة الخاصة بالمشروع واختر New > Java Class ثم اكتب اسم الصنف الجديد CustomArrayAdapter. يجب أن يرث هذا الصنف الفرعي من الصنف الأساسي ArrayAdapter. public class CustomArrayAdapter extends ArrayAdapter ثم نقوم بكتابة دالة البناء الخاصة بهذا الصنف ونستدعى في بدايتها دالة البناء الخاصة بالصنف الأب، وتحتاج دالة البناء لكائن من الصنف Context وهو السياق الذي سيعمل فيه هذا الـ Adapter أي النشاط الذي سيعمل به، كما يحتاج إلى واجهة المستخدم التي تكون شكل الصف، وقائمة البيانات التي سيتم عرضها. private Context con; private ArrayList<ListItem> data; private int resLayout; public CustomArrayAdapter(Context context, int resource, List objects) { super(context, resource,objects); con = context; resLayout = resource; data =(ArrayList) objects; } يوجد تابع داخل ArrayAdapter يُدعى ()getCount ويتم استخدامه بشكل تلقائي عندما يريد الكائن الذي سنصنعه من CustomArrayAdapter معرفة عدد العناصر التي سيقوم بعرضها داخل قائمة العرض، لذا فهو يستدعي التابع ()size باستخدام الكائن الخاص بالبيانات. @Override public int getCount() { return data.size(); } وبداخل الصنف الأب ArrayAdapter يوجد أيضًا تابع يُدعى ()getView، وهو المسؤول عن تكوين الشكل الخاص بالصف المعروض في قائمة العرض لذا كي نصنع الصف الخاص بنا يجب علينا تعديل هذا التابع. ولتعديل التابع نقوم بكتابته بطريقتنا الخاصة كما يلي: @Override public View getView(int position, View convertView, ViewGroup parent) { inflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootView = inflater.inflate(resLayout,null); ImageView img = (ImageView) rootView.findViewById(R.id.imgview); TextView title = (TextView) rootView.findViewById(R.id.titleview); TextView desc = (TextView) rootView.findViewById(R.id.descview); ListItem item = data.get(position); img.setImageResource(item.imgSrc); title.setText(item.title); desc.setText(item.desc); return rootView; } داخل التابع نبدأ أولًا بطلب الكائن LayoutInflater وهو المسؤول عن تحويل ملف XML إلى كائن من النوع View وللحصول عليه نستخدم التابع ()getSystemService والذي يوفر مجموعة مختلفة من الخدمات منها الكائن LayoutInflater، ويتم تحديد نوع الخدمة المطلوبة عن طريق تمرير ثابت محدد وفي هذه الحالة هو Context.LAYOUT_INFLATER_SERVICE. بعد الحصول على هذا الكائن نستدعي التابع ()inflate ونمرر له ملف XML الذي نرغب في تحويله إلى كائن في ملف الجافا. inflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootView = inflater.inflate(resLayout,null); الآن لربط عناصر الواجهة بالشيفرة نستخدم التابع ()findViewById ولكنا هنا لا نستطيع استخدامه مباشرة كما كنا نفعل عند وجودنا داخل شيفرة النشاط، لذا يجب أن نستدعي التابع باستخدام الكائن من الصنف View الذي حصلنا عليه من التابع ()inflate. ImageView img = (ImageView) rootView.findViewById(R.id.imgview); TextView title = (TextView) rootView.findViewById(R.id.titleview); TextView desc = (TextView) rootView.findViewById(R.id.descview); أخيرًا نأتي بالبيانات المناسبة للعنصر من داخل الـ ArrayList ثم نضع بداخل كل عنصر من عناصر الواجهة البيانات الخاصة به. ListItem item = data.get(position); img.setImageResource(item.imgSrc); title.setText(item.title); desc.setText(item.desc); لتصبح الشيفرة النهائية داخل الصنف CustomArrayAdapter: package apps.noby.customlistviewexample; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class CustomArrayAdapter extends ArrayAdapter { private Context con; private ArrayList<ListItem> data; private LayoutInflater inflater; private int resLayout; public CustomArrayAdapter(Context context, int resource, List objects) { super(context, resource,objects); con = context; data =(ArrayList) objects; resLayout = resource; } @Override public int getCount() { return data.size(); } @Override public View getView(int position, View convertView, ViewGroup parent) { inflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootView = inflater.inflate(resLayout,null); ImageView img = (ImageView) rootView.findViewById(R.id.imgview); TextView title = (TextView) rootView.findViewById(R.id.titleview); TextView desc = (TextView) rootView.findViewById(R.id.descview); ListItem item = data.get(position); img.setImageResource(item.imgSrc); title.setText(item.title); desc.setText(item.desc); return rootView; } } نعود مجددًا إلى ملف MainActivity.java لإنشاء كائن من الصنف الذي صنعناه: private CustomArrayAdapter adapter; adapter = new CustomArrayAdapter(this,R.layout.row_item,items); ثالثًا نربط قائمة العرض بالشيفرة ونستدعي التابع ()setAdapter: private ListView customLV; customLV = (ListView) findViewById(R.id.listview); customLV.setAdapter(adapter); بهذا نكون قد انتهينا من تكوين قائمة العرض المطلوبة، يتبقى أن نجعل عناصر قائمة العرض تستجيب عند الضغط عليها وتقوم بفتح نشاط جديد، لذا أولًا نقوم بعمل نشاط جديد بالضغط بالزر اليمن على اسم الحزمة واختيار: New > Activity > Empty Activity ونسميها DescriptionActivity. ولفتح هذا النشاط عند الضغط على عناصر قائمة العرض نقوم باستدعاء التابع ()setOnItemClickListener وبداخله نفتح النشاط الجديد ونرسل إليه اسم العنصر الذي قام بفتحه حتى يعرضه. package apps.noby.customlistviewexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView customLV; private CustomArrayAdapter adapter; private ArrayList<ListItem> items; String [] colorsArray = {"Red","Yellow","Blue","Black","Green","Brown","Grey"}; int [] colorsImage = {R.drawable.red,R.drawable.yellow,R.drawable.blue,R.drawable.black,R.drawable.green,R.drawable.brown,R.drawable.grey}; public static final String COLOR_KEY = "colorName"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); items = new ArrayList<ListItem>(); for(int i=0;i<colorsArray.length;i++){ ListItem item= new ListItem(); item.imgSrc=colorsImage; item.title = colorsArray; item.desc = colorsArray + " Color!"; items.add(item); } customLV = (ListView) findViewById(R.id.listview); adapter = new CustomArrayAdapter(this,R.layout.row_item,items); customLV.setAdapter(adapter); customLV.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Intent intent = new Intent(MainActivity.this,DescriptionActivity.class); intent.putExtra(COLOR_KEY,colorsArray[position]); startActivity(intent); } }); } } أخيرًا نقوم بتهيئة واجهة المستخدم الخاصة بالنشاط الجديد لتعرض النص المرسل إليها. <?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:gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/txt" android:textSize="20sp"/> </LinearLayout> ولعرض النص داخل الـ TextView نقوم في الشيفرة باستدعاء الـ Intent الذي قام بفتح النشاط واستخراج منه البيانات المرسلة وكتابتها على TextView كما يلي: package apps.noby.customlistviewexample; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class DescriptionActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_description); String str = getIntent().getStringExtra(MainActivity.COLOR_KEY); TextView tv = (TextView) findViewById(R.id.txt); tv.setText("Welcome to " + str + " Color Home!!"); } } والآن نقوم بتجربة التطبيق على المحاكي حتى نتأكد من عمله بالشكل المطلوب. البحث داخل عناصر قائمة العرض التي تم تخصيصها تطبيق 6 في هذا التطبيق سنكرر ما قمنا به سابقًا من بحث عن نص معين داخل قائمة العرض ولكن الاختلاف هنا سيكون أن قائمة العرض ليست بهذه البساطة وتتكون من عنصر واحد فقط وهو النص، ولكنها تتكون من صور وعدة نصوص مختلفة لذا ينبغي علينا أن نصنع بأنفسنا طريقتنا الخاصة للبحث وتنقية العناصر من القائمة وترك العناصر التي يتم البحث عنها. أولًا نقوم بتغيير ملف الواجهة activity_main.xml وإضافة عنصر 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"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_search_black_24dp" android:hint="Search..." android:id="@+id/searchedittext"/> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"/> </LinearLayout> ثانيًا نربط عنصر EditText في الشيفرة الأساسية ونستدعي التابع ()addTextChangedListener وبداخل الدالة ()onTextChanged نستدعي التابع الخاص بالتنقية كما سبق. package apps.noby.customlistviewexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView customLV; private CustomArrayAdapter adapter; private ArrayList<ListItem> items; String[] colorsArray = {"Red", "Yellow", "Blue", "Black", "Green", "Brown", "Grey"}; int[] colorsImage = {R.drawable.red, R.drawable.yellow, R.drawable.blue, R.drawable.black, R.drawable.green, R.drawable.brown, R.drawable.grey}; public static final String COLOR_KEY = "colorName"; EditText searchET; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); items = new ArrayList<ListItem>(); for (int i = 0; i < colorsArray.length; i++) { ListItem item = new ListItem(); item.imgSrc = colorsImage; item.title = colorsArray; item.desc = colorsArray + " Color!"; items.add(item); } searchET = (EditText) findViewById(R.id.searchedittext); customLV = (ListView) findViewById(R.id.listview); adapter = new CustomArrayAdapter(this, R.layout.row_item, items); customLV.setAdapter(adapter); customLV.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Intent intent = new Intent(MainActivity.this, DescriptionActivity.class); intent.putExtra(COLOR_KEY, colorsArray[position]); startActivity(intent); } }); searchET.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { adapter.getFilter().filter(s); } @Override public void afterTextChanged(Editable s) { } }); } } أخيرًا يتبقى أن نقوم بكتابة تابع التنقية الخاص بنا، وسنقوم بكتابته داخل الصنف CustomArrayAdapter. داخل CustomArrayAdapter نقوم بإنشاء كائن آخر من ArrayList ونجعله يساوي البيانات التي سنضعها في قائمة العرض، مثله كالكائن data. private ArrayList<ListItem> dataTemp; public CustomArrayAdapter(Context context, int resource, ArrayList<ListItem> objects) { super(context, resource,objects); con = context; data = objects; resLayout = resource; dataTemp = objects; } بعد ذلك نعيد كتابة التابع الخاص بالتنقية والمتواجد أيضًا داخل الأب Arrayadapter لذا سنقوم بعمل Override لهذا التابع. والتابع هو ()getFilter ويعيد كائن من الصنف Filter وبداخل هذا الصنف تظهر لدينا دالتين جديدتين كما يلي: @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { } @Override protected void publishResults(CharSequence constraint, FilterResults results) { } }; } الأولى ()performFiltering هي المسؤولة عن عملية التنقية ونكتب بداخلها كيف تتم تنقية العناصر وما هي العناصر التي ستظهر عند كتابة نص معين ويتم تخزين النص الذي تم كتابته وعلى أساسه تتم عملية التنقية داخل المتغير constraint. الثانية ()publishResults هي المسؤولة عن تغيير محتوي البيانات التي تعرضها قائمة العرض حيث تأتي النتائج بعد التنقية داخل الكائن results. سنبدأ أولًا بكتابة طريقة التنقية التي سنتبعها وهي، نتأكد أن النص الذي ستتم التنقية على أساسه وممثل في المتغير constraint ليس فارغًا وذلك عن طريق الجملة المنطقية التالية: constraint == null || constraint.length() == 0 وتعني هل المتغير constraint لم يُخزن بداخله أي بيانات أو تم تخزين نص فارغ، لذا سنضع هذه الجملة داخل الجملة الشرطية if وإن تحققت فذلك يعني أنه لا داعي للتنقية وستظل عناصر القائمة كما هي، أما إذا كان خلاف ذلك ويوجد نص فسنقوم بعملية التنقية. FilterResults results = new FilterResults(); if(constraint == null || constraint.length() == 0){ results.values = dataTemp; results.count = dataTemp.size(); } else{ } return results; لاحظ أن الكائن results هو المتغير الذي سنخزن بداخله ناتج التنقية، ويتكون من عنصرين مهمين الأول results.values وهنا يتم تخزين البيانات الجديدة و results.count ويتم تخزين عددهم. طريقة التنقية ستكون كالتالي: نصنع حاوية جديدة لتخزين العناصر الناتجة من التنقية. لكل عنصر من العناصر القديمة في قائمة العرض نقوم بمقارنته بالنص المكتوب لنرى هل يحتوي على حروفه. إن كان يحتوي على أحد حروفه ننقله إلى حاوية التخزين الجديدة التي صنعناها. بعد الانتهاء من كافة العناصر نعيد النتيجة ليتم تحديث قائمة العرض بالعناصر الجديدة. ArrayList<ListItem> filterList = new ArrayList<ListItem>(); for (int i = 0; i < dataTemp.size(); i++) { ListItem item = dataTemp.get(i); if ( (item.title.toLowerCase()).contains(constraint.toString().toLowerCase())) { ListItem filterItem = new ListItem(); filterItem.imgSrc = dataTemp.get(i).imgSrc; filterItem.title = dataTemp.get(i).title; filterItem.desc = dataTemp.get(i).desc; filterList.add(filterItem); } } results.values = filterList; results.count = filterList.size(); لاحظ أننا نقوم بالتنقية على أساس النص المخزن داخل title، وأننا نقوم بتحويل هذا النص إلى حروف صغيرة ومقارنته بالنص الذي نبحث عنه أيضًا بعد تحويله لحروف صغيرة لتوحيد طريقة كتابة الكلمة. ونستخدم التابع ()contains والذي يتأكد هل الكائن الذي قام باستدعاء هذا التابع يحتوي على النص الذي نمرره للتابع أم لا، إن كان يحتويه فسيعيد القيمة المنطقية true أما إذا كان لا يحتويه فسيعيد false. بعد ذلك ننتقل إلى الدالة الأخرى لنشر النتائج الجديدة في قائمة العرض، ونقوم بنقل البيانات المخزنة في results.values إلى المتغير data حتى يتم تحديث قائمة العرض باستدعاء التابع ()getView بعد استدعاء ()notifyDataChanged. data = (ArrayList<ListItem>) results.values; notifyDataSetChanged(); لتصبح الشيفرة النهائية للصنف CustomArrayAdapter بعد التعديل: package apps.noby.customlistviewexample; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Filter; import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; public class CustomArrayAdapter extends ArrayAdapter{ private Context con; private ArrayList<ListItem> data; private ArrayList<ListItem> dataTemp; private LayoutInflater inflater; private int resLayout; public CustomArrayAdapter(Context context, int resource, ArrayList<ListItem> objects) { super(context, resource,objects); con = context; data = objects; resLayout = resource; dataTemp = objects; } @Override public int getCount() { return data.size(); } @Override public View getView(int position, View convertView, ViewGroup parent) { inflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootView = inflater.inflate(resLayout,null); ImageView img = (ImageView) rootView.findViewById(R.id.imgview); TextView title = (TextView) rootView.findViewById(R.id.titleview); TextView desc = (TextView) rootView.findViewById(R.id.descview); ListItem item = data.get(position); img.setImageResource(item.imgSrc); title.setText(item.title); desc.setText(item.desc); return rootView; } @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); if (constraint == null || constraint.length() == 0) { results.values = dataTemp; results.count = dataTemp.size(); } else{ ArrayList<ListItem> filterList = new ArrayList<ListItem>(); for (int i = 0; i < dataTemp.size(); i++) { ListItem item = dataTemp.get(i); if ( (item.title.toLowerCase()).contains(constraint.toString().toLowerCase())) { ListItem filterItem = new ListItem(); filterItem.imgSrc = dataTemp.get(i).imgSrc; filterItem.title = dataTemp.get(i).title; filterItem.desc = dataTemp.get(i).desc; filterList.add(filterItem); } } results.values = filterList; results.count = filterList.size(); } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { data = (ArrayList<ListItem>) results.values; notifyDataSetChanged(); } }; } } الآن قم بتجربة التطبيق على المحاكي للتأكد انه يعمل كما ينبغي. بهذا نكون قد وصلنا إلى نهاية هذا الدرس، في انتظار تجربتكم وآرائكم.
  2. العناصر الأساسية في تطبيقات أندرويد تتكون تطبيقات أندرويد من أربعة عناصر أساسية لكل منها دورها الخاص الذي تستطيع القيام به، ويتم استخدامها بناءً على الغرض أو المهمة المطلوب تنفيذها وهي: النشاطات Activities يُمثل واجهة واحدة والتي تظهر أمام المستخدم ويتفاعل معها، وقد يتكون التطبيق من عدة واجهات مختلفة وبالتالي عدة Activities مختلفة، فمثلًا تطبيق البريد الإلكتروني لديه واجهة خاصة بعرض البريد الجديد وعند قراءة محتوى رسالة محددة تظهر لك في واجهة أخرى مخصصة لذلك وإذا أردت إرسال رسالة بريد جديدة يظهر لك واجهة جديدة لقيام بهذه المهمة. فأغلب تطبيقات أندرويد تتكون من عدة نشاطات Activities ولكن عند فتح التطبيق هناك واجهة واحدة دائمًا ما تكون هي الواجهة الرئيسية والتي تُعرض أمام المستخدم وينتقل من خلالها إلى باقي النشاطات Activities الخاصة بالتطبيق. الخدمات Services يُستخدم هذا العنصر بشكل أساسي عند القيام بعمليات طويلة ويتم تنفيذها في الخلفية دون تعطيل الواجهة الرئيسية أو تعامل المستخدم مع التطبيق، ومثال على ذلك عند تشغيل تطبيق المتصفح وتحميل ملف من أحد المواقع فيتم تنفيذ هذه المهمة في الخلفية ويتفاعل المستخدم مع المتصفح في ذات الوقت ولا ينتظر انتهاء التحميل، وذلك لأن هذه المهمة تمت من خلال العنصر Service. مستقبلات البث Broadcast Receivers هي حلقة الاتصال بين التطبيق ونظام التشغيل. ويستخدم هذا العنصر لاستقبال الرسائل التي يبُثها نظام التشغيل أو تطبيق آخر، فمثلًا عندما يستقبل نظام التشغيل مكالمة هاتفية يقوم ببث رسالة لكل التطبيقات المتواجدة على الهاتف لإعلامهم بوجود مكالمة تحتاج إلى التطبيق الخاص بالاتصال والذي يستطيع التعامل معها وعرض بيناتها أمام المستخدم ويكون في تطبيق الاتصال هذا العنصر لاستقبال الرسالة من نظام التشغيل والتعامل معها، أو عندما ينتهي تطبيق من تحميل صورة يُرسل لباقي لتطبيقات رسالة أن هناك صوة جديدة على الهاتف يمكن للتطبيق المسؤول عنها التعامل معها. لذا فهذا العنصر هو المسؤول عن استقبال هذا النوع من الرسائل واتخاذ القرار المناسب. مزودو المحتوى Content Providers يوفر هذا العنصر طريقة لمشاركة البيانات بين التطبيقات المختلفة، مثال على ذلك قاعدة البيانات الخاصة بجهات الاتصال على هاتفك نجد أن هناك بعض التطبيقات التي تستخدمها في تقديم خدماتها. النشاطات Activities نبدأ أولًا بالنشاطات Activities حيث لا يخلو تطبيق من واجهة للمستخدم، لصنع نشاط في التطبيق يتم ذلك عن طريق كتابة صنف جديد Class يرث من صنف آخر اسمه Activity، وهذا الصنف هو الأب دائمًا عند عمل نشاط جديدة ويحتوي على التوابع Methods الخاصة بالنشاط والمكوّنة لها. public class MainActivity extends Activity { } إن كانت لديك خبرة سابقة في التعامل مع أي لغة برمجة فمن المؤكد أنك رأيت الدالة ()main والتي تُمثل نقطة البداية للبرنامج، ولكن في أندرويد هناك نشاط رئيسي يُمثل نقطة البداية للتطبيق ولكل نشاط دورة حياة خاصة به، وتتكون من مجموعة من التوابع methods يتم استدعاؤها لحظة إنشاء النّشاط ومجموعة من التوابع التي يتم استدعاؤها لحظة إزالة النّشاط ونجدها في الصورة التالية. دورة حياة النشاط ()onCreate: ويتم استدعاؤه عند إنشاء النشاط أول مرة، ويتم بداخلها ربط الواجهة بالشيفرة التي تتحكم في التطبيق. ()onStart: ويتم استدعاؤه عندما تبدأ الواجهة في الظهور وبعد الانتهاء من ()onCreate. ()onResume: ويتم استدعاؤه عندما تظهر الواجهة الخاصة بالـ Activity ويتفاعل معها المستخدم. ()onPause: ويتم استدعاؤه عندما تبدأ الواجهة في الاختفاء أو تظهر بشكل جزئي ولا يمكن للمستخدم التفاعل معها لوجود شيء ما يحجبها وعند إزالة ما يحجبها يتم استدعاء ()onResume مرة أخرى. ()onStop: ويتم استدعاؤه عندما تختفي الواجهة من أمام المستخدم نتيجة للانتقال إلى واجهة أخرى داخل نفس التطبيق أو لغلق التطبيق والانتقال لتطبيق آخر. ()onRestart: ويتم استدعاؤه عند الرجوع مرة أخرى إلى نفس الواجهة ثم يتم استدعاء ()onStart. ()onDestroy: ويتم استدعاؤه قبل تدمير الواجهة وإزالتها من الذاكرة ويتم ذلك عندما يحتاج نظام التشغيل إلى المزيد من المساحة في الذاكرة أو إزالتها برمجيًا من الذاكرة. مثال 1 الآن قم بفتح Android Studio وسنقوم بعمل تطبيق جديد اسمه "Activity Life Cycle" حتى نرى كيف ينادي النظام التّوابع الخاصة بالنّشاط. سنختار Empty Activity وندع باقي الاختيارات كما هي. بعد الانتهاء ستجد في المجلد 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(); ومن خلال هذا السطر نقوم بإظهار رسالة للمستخدم لمدة محددة كما في الصورة. والآن نقوم بنفس الشيء لباقي التوابع: 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(); } } ثم نقوم بتشغيله على المحاكي لنرى التطبيق وهو يعمل. ملاحظات التابع ()onCreate هو التابع الوحيد الذي نقوم بداخله بتعريف واجهة المستخدم من داخل المجلد layout باستخدام التابع ()setContentView. لا يشترط عند كتابة التوابع كتابتها بترتيب استدعائها. يجب أن يحتوي النّشاط على التابع ()onCreate وإلا لن تعمل، وباقي التوابع نستخدم منها فقط ما نحتاجه لأداء مهمة محددة في وقت محدد، فمثلًا إذا كان التطبيق يستخدم الكاميرا وقام المستخدم بغلق التطبيق أو الانتقال إلى تطبيق أخر فيجب عند هذه الحالة قبل اختفاء النّشاط غلق الكاميرا حتى لا يتم إهدار الموارد دون استخدام، فأنسب مكان لذلك هو التابع ()onStop فهو الذي يُعبر عن هذه الحالة . عند صنع نشاط جديد يجب تعريفه في ملف 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 سنقوم بصنع تطبيق كما في الصورة التالية يحتوي على نص وزر عند الضغط عليه يتغير النص إلى كلمة أخرى من اختيارنا. أولًا نقوم بصنع الواجهة الخاصة بالتطبيق في ملف 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 Text” android: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); } }); } } عند تشغيل التطبيق على المحاكي والضغط على الزر نجد أن التطبيق يعمل بالشكل المطلوب. ملاحظات يتم تعريف نوع المتغيرات خارج التابع onCreate. تستخدم import عندما يكون هناك صنف متواجد في إحدى المكتبات ونريد تضمينه داخل تطبيقنا فنقوم بكتابة بجانب الأمر import ومكان تواجد هذا الصنف. يجب ذكر اسم الحزمة في بداية الشيفرة الخاصة بالنّشاط. يمكن تمرير النص للتابع setText في سطر واحد مثل: tv.setText("Hello Android Developer"); بدلًا من تخزين قيمة النص في متغير ثم تمرير هذا المتغير. مثال 3 سنضيف في هذا المثال EditText ليستطيع المستخدم كتابة نص ثم سنقوم بعرضه له بدلًا من عرض نص محدد مسبقًا. أولًا نبدأ بصنع الواجهة ولن تختلف عن واجهة المثال السابق إلا بزيادة عنصر من النوع 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 + "!"); } }); } } ثم نقوم بتجربة التطبيق على المحاكي. مثال 4 سنقوم بصنع تطبيق يظهر صورة عند الضغط على الزر ثم يخفيها عند الضغط عليه مرة أخرى. أولًا نبدأ بصنع واجهة المستخدم: <?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"); } } نستخدم هذه الدالة والتي يجب أن تكون كالتالي: الوصول لها Public ولا ترجع أية قيمة (يعني تُرجع void) الاسم الخاص بها نفس الاسم الذي كتبناه سابقًا للخاصية onClick. تمرير للدالة عنصر من النوع 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"); } } } ثم نقوم بتجربة التطبيق على المحاكي. بهذا نكون قد وصلنا إلى نهاية هذا الدرس، في انتظار تجربتكم وآرائكم.
  3. بعد أن أنشأنا أول مشروع في Android Studio وقمنا بتجربة التطبيق على المحاكي سنتعلم الآن أساسيات التعامل مع واجهة المستخدم وكيفية إنشاء العناصر المختلفة بداخلها. طرق إنشاء واجهة المستخدم لتطبيق أندرويد في نظام تشغيل أندرويد هناك طريقتان لإنشاء واجهة المستخدم الخاصة بالتطبيق: التعريف في الملف الخاص بها layout باستخدام لغة XML: وتتميز هذه الطريقة بأنها تقوم بفصل عناصر الواجهة عن الشيفرات البرمجية بلغة الجافا والتي تحدد وظيفة التطبيق الأصلية مما يُسهل عملية تطوير التطبيقات واكتشاف الأخطاء وتصحيحها وتجعل التطبيق أكثر مرونة لدعم أحجام الشاشات المختلفة واللغات المختلفة. تعريف عناصر واجهة المستخدم باستخدام الشيفرات البرمجية بلغة الجافا حيث تتكون الواجهة وقت تشغيل التطبيق وعمله لدى المستخدم. كما يمكنك استخدام هاتين الطريقتين معًا عن طريق تعريف العناصر في ملف layout وباستخدام الشيفرة البرمجية تقوم بتغيير خصائصها بناءً على تفاعل المستخدم مع هذه العناصر، وتلك هي الطريقة التي سنقوم باستخدامها. عناصر واجهة المستخدم تنقسم عناصر واجهة المستخدم إلى نوعين View و ViewGroup: View هو عنصر يقوم برسم شيء ما على الشاشة حيث يستطيع المستخدم التفاعل مع هذا الشيء. ViewGroup هو حاوية غير مرئية تستطيع أن تحمل بداخلها العناصر المكوَنة للواجهة. ويتميز كل نوع من ViewGroup بطريقة فريدة ومختلفة لعرض العناصر الأخرى بداخله وتُسمى العناصر المتواجدة داخل ViewGroup بالأبناء. يمكن أن يحتوي عنصر ViewGroup على عناصر من النوع ViewGroup أيضًا أو من النوع View وذلك للوصول إلى التصميم المنشود لواجهة المستخدم كما توضح الصورة التالية: يوفر أندرويد للمطورين مجموعة من Views و ViewGroup الجاهزة والتي تقوم بالوظائف الشائعة في التطبيقات. أمثلة على ViewGroup Linear Layout: ويتم ترتيب العناصر بداخله في اتجاه واحد إما بشكل أفقي أو رأسي. Relative Layout: ويتم ترتيب العناصر بداخله نسبة إلى عنصر أخر ويتم استخدامه عند صنع واجهة أكثر تعقيدا يصعُب معها استخدام Linear Layout. أمثلة على View Button TextView ImageView أساسيات تصميم الواجهات باستخدام XML توفر لغة XML طريقة سهلة وسريعة لصنع واجهة المستخدم وتتشابه مع طريقة صناعة صفحات الويب باستخدام HTML. كما ذكرنا سابقًا تتواجد الملفات الخاصة بالواجهة في مجلد res/layout بامتداد XML. يجب أن تحتوي ملفات XML على عنصر يسمى root ويعتبر هذا العنصر الحاوية الرئيسية لباقي العناصر ويكون هذا العنصر إما ViewGroup أوView، كما يجب أن يحتوي على XML namespace وهو من المعايير القياسية الخاصة بـ XML. تعريف الـ namespace بسيط وثابت لكل الـ root elements ويكون على النحو التالي xmlns:android="http://schemas.android.com/apk/res/android" وبعد تعريف الـ root element يمكنك بعدها إضافة عناصر أخرى لاستكمال واجهة المستخدم. وفي أغلب التطبيقات يكون الـ root من النوع ViewGroup لقدرته على إضافة أبناء له، على عكس View والذي لا يمكن أن يحتوي على أبناء. يتكون كل عنصر من عناصر XML من وسم الفتح <ElementName>، ووسم الغلق <ElementName/>. وبين وسم الفتح والغلق يتم وضع العناصر الأخرى والتي تعتبر أبناء ViewGroup، وبما أنه لا يوجد للعنصر View لأبناء فيمكن الاستغناء فيه عن وسم الغلق واستخدام وسم الغلق الذاتي </ElementName>. لتوضيح الأمر أكثر سنقوم بصنع واجهة مستخدم مشابهة لتطبيق أهلًا بالعالم Hello World. قم بفتح الملف content_main.xml المتواجد بداخل المجلد res/layout، ثم قم بمسح كافة الشيفرة المكتوبة. بعد عنصر root الخاص بالواجهة أضف عنصر ViewGroup من نوع LinearLayout كالتالي: <LinearLayout> قم بتحديد XML namespace الخاص بالعنصر LinearLayout: <LinearLayout xmlns:android=http://schemas.android.com/apk/res/android > لكل عنصر من عناصر واجهة المستخدم سواء كان View أو ViewGroup العديد من الخصائص والتي يمكن التحكم بها وتخصيصها عن طريق XML أو عن طريق شيفرة الجافا وقد تكون هذه الخصائص خاصة بهذا العنصر فقط أو خصائص مشتركة لدى أكثر من عنصر. ومن الخصائص الهامة تحديد العرض والارتفاع الخاصين بالعنصر، ولتحديد العرض للعنصر نستخدم الخاصية layout_width: android:layout_width="Value" ونستبدل Value بقيمة العرض، وهناك عدة طرق لتحديد قيمة العرض، فيمكن تحديدها عن طريق قيم معرَفة مسبقًا للنظام مثل "match_parent" وتعني أن يكون عرض العنصر نفس عرض العنصر الأب، وإذا كان العنصر الأب هو root فالأب هنا المقصود به شاشة الهاتف. وباستخدام نفس الطريقة يتم تحديد الارتفاع الخاص بالعنصر عن طريق خاصية layout_height: android:layout_height ="Value" ليصبح الشكل النهائي للعنصر: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> </LinearLayout> لاحظ أنه يتم تحديد كافة الخصائص الخاصة بالعنصر داخل وسم الفتح ويتم أيضًا وضع XML namespace معهم. سنقوم بإضافة عنصر أخر للواجهة من نوع View وهو TextView والذي يستطيع أن يحمل بداخله نصًا ويمكن للمستخدم قراءته ولا يمكنه تغييره: <TextView /> سنقوم بتحديد الخصائص الخاصة بهذا العنصر وكما ذكرنا أن خاصتيَ العرض والارتفاع من الخصائص الهامة التي ينبغي تحديدها لكافة العناصر. <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> كان من الممكن استخدام قيمة "match_parent" ولكننا قمنا باستخدام "wrap_content" والتي تعني أن يكون العرض أو الارتفاع يشغل القدر المطلوب للنص فقط، أما إذا استخدمنا "match_parent" فسيشغل العنصر كل المساحة الخاصة بالأب والذي هو LinearLayout والذي يشغل مساحة الشاشة كلها كما حددنا ذلك سابقًا. لتحديد النص المطلوب كتابته داخل العنصر، نقوم بإضافة السطر التالي: android:text="Hello World!" هذا هو الشكل الكلي لملف XML: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> </LinearLayout> الآن قم بتشغيل التطبيق على المحاكي وستجده يعمل بالشكل المطلوب. ملاحظات العنصر TextView متواجد بين وسميَ الفتح والغلق الخاصين بـ LinearLayout ويُعتبر TextView ابنًا لـ LinearLayout. يستخدم العنصر TextView وسم الغلق الذاتي. يتم تحديد الخصائص للعنصر داخل وسم الفتح الخاص به فقط. التطبيق الثاني سنقوم بتطوير المثال السابق واستخدام عناصر أخرى في التطبيق لصنع واجهة مثل الصورة التالية: الاختلاف هنا في إضافة عنصر جديد للواجهة وهو Button. لإضافة هذا العنصر يتم استخدام العنصر </ Button> وإضافته داخل LinearLayout. نقوم بتحديد الطول، الارتفاع والنص الخاص بالزر كالتالي: <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Click me"/> سيظهر الزر بجانب النص السابق: ولتغيير موضع الزر يجب تغيير اتجاه العناصر داخل عنصر LinearLayout، ولتغيير الاتجاه للرأسي بدلًا من الأفقي نستخدم الخاصية orientation: android:orientation="vertical" حيث أن الاتجاه يكون أفقيًا بشكل افتراضي. ليصبح ملف XML على النحو التالي: <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!" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Click me"/> </LinearLayout> ملاحظات أي عنصر من النوع View يستخدم وسم الغلق الذاتي لأنه لا يستطيع أن يحتوي على أبناء أو عناصر أخرى بداخله. إذا قمت بكتابة العنصر Button أولًا بداخل LinearLayout ثم العنصر TextView سيتم تغيير الترتيب في الواجهة أيضًا. التطبيق الثالث يحتوي هذا التطبيق على نص "Android Image" وصورة للأندرويد وزر يتم ترتيبها بشكل رأسي. لصنع واجهة المستخدم سنختار LinearLayout كعنصر root وتغيير الاتجاه الخاص به للرأسي. نقوم بعدها بإضافة عنصر TextView وكتابة النص الخاص به، ولكن يمكن ملاحظة أن حجم الخط الخاص بالنص أكبر من الأمثلة السابقة لذا يجب تغيير حجم الخط عن طريق الخاصية textSize: android:textSize="40sp" ويتم تحديد الخط بوحدة "sp" وهي وحدة خاصة في نظام أندرويد لتحديد حجم الخط دون الاعتماد على كثافة البكسل المكوَنة للشاشة ليظهر النص دائمًا بنفس الحجم باختلاف أحجام شاشات الهواتف. لتغيير موضع النص ليكون في منتصف الواجهة نقوم باستخدام خاصية الجاذبية layout_gravity: android:layout_gravity="center" نقوم بإضافة العنصر ImageView لعرض الصور وتحديد الطول والارتفاع كالسابق، ولتحديد الصورة المعروضة بداخله نقوم بوضع الصورة في الملف res/drawable وعرضها داخل العنصر باستخدام الخاصية src: android:src="@drawable/andimg" تستخدم @ للإشارة إلى عنصر داخل الملف وبعدها يتم كتابة اسم الملف وهو drawable ثم تحديد اسم الصورة المتواجدة بداخله. نضيف بعدها العنصر Button ونقوم بتحديد النص الخاص به مع ملاحظة أن العرض الخاص به يستغل كافة مساحة الشاشة وليس على قدر المحتوى فقط. ليصبح ملف XML على النحو التالي: <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="Android Image" android:textSize="40sp" android:layout_gravity="center" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/andimg"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Say Hi"/> </LinearLayout> التطبيق الرابع نريد في هذا التطبيق صُنع واجهة مركبة كما في الصورة التالية: سيكون الـ root مثل الأمثلة السابقة من النوع LinearLayout ولكن هل اتجاه العناصر بداخله أفقي أم رأسي؟ ستجد العناصر متواجدة في الاتجاهين. لصنع هذه الواجهة سنستخدم أكثر من ViewGroup من النوع LinearLayout ونغير اتجاهاتها لصنع الشكل المطلوب كما في الصورة التالية: سيكون العنصر root من النوع LinearLayout والاتجاه بداخله رأسي، ويحتوي على LinearLayout آخر الاتجاه بداخله أفقي ونضع بداخله العنصرين المتجاورين كما في الصورة السابقة. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> </LinearLayout> </LinearLayout> نضع العنصرين ImageView و EditText داخل العنصر LinearLayout الثاني. نقوم بتحديد العرض والارتفاع واختيار الصورة من ملف drawable للعنصر ImageView مثل ما فعلنا في التطبيق السابق، وبالنسبة للعنصر الآخر EditText والذي يٌستخدم لإدخال نص من المستخدم، نقوم بتحديد عرضه وارتفاعه، بالإضافة إلى عرض نص يعبر عن المحتوى الذي يجب على المستخدم إدخاله باستخدام الخاصية hint: android:hint="Write your name here" نُضيف العنصر الأخير من النوع Button داخل LinearLayout الأول ولوضعه جهة اليمين نستخدم الخاصية layout_gravity: android:layout_gravity="right" ليُصبح ملف XML على الشكل التالي: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Write your name here"/> </LinearLayout> <Button android:layout_width="wrap_content" android:layout_height="70dp" android:text="Done" android:layout_gravity="right"/> </LinearLayout> ملاحظات تم تحديد الارتفاع الخاص بـ LinearLayout الثاني بـ "wrap_content" حتى يشغل الارتفاع الخاص بالعناصر فقط ولا يشغل مساحة الشاشة كلها. تم استخدام طريقة مختلفة لتحديد الارتفاع الخاص بـالعنصر Button، وتستخدم هذه الطريقة في حالة لم نرد استخدام القيم المعرَفة داخل النظام مثل "match_parent" أو "wrap_content" واستخدام قيم مختلفة. وتُستخدم الوحدة "dp" كوحدة لتحديد الأبعاد دون الاعتماد على كثافة البكسل المكوَنة للشاشة ليظهر العنصر بنفس الأبعاد دائمًا باختلاف أحجام شاشات الهواتف. التطبيق الخامس هناك طريقة أخرى لإنشاء واجهة المستخدم كما في التطبيق السابق، حيث نقوم باختيار root من النوع RelativeLayout للوصول لنفس الشكل أيضًا كما في الصورة السابقة. في RelativeLayout يتم وضع العناصر نسبة إلى عناصر أخرى متواجدة لذلك ينبغي تحديد لكل عنصر ID مميز له لأن عند وضع عنصرين من نفس النوع (Button مثلا) سيكون اسم كل منهما Button ولا نستطيع عندها التفريق بين الزر الأول أو الثاني وللتفرقة بينهم يتم استخدام خاصية id: android:id="@+id/btn1" ويتم كتابة ID الخاص بالعنصر بعد كلمة /id+@ ويكون هذا ID مميز لهذا العنصر فقط ولا يتشابه معه عنصر آخر. وهناك استخدام آخر لـ ID عند التحكم في العناصر باستخدام شيفرة الجافا. وبهذا يصبح ملف XML على الشكل التالي: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" android:id="@+id/img" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Write your name here" android:id="@+id/edttxt" android:layout_toRightOf="@+id/img" /> <Button android:layout_width="wrap_content" android:layout_height="70dp" android:text="Done" android:id="@+id/btn1" android:layout_below="@+id/edttxt" android:layout_alignParentRight="true"/> </RelativeLayout> عند تشغيل التطبيق على المحاكي سيقوم برسم نفس الشكل السابق. ملاحظات تم تحديد ID خاص لكل العناصر. هناك بعض الخصائص التي تحدد موضع كل عنصر بالنسبة لعنصر آخر. مثل الخاصية layout_toRightOf المستخدمة للعنصر EditText وذلك لوضعه بجانب العنصر ImageView جهة اليمين: android:layout_toRightOf="@+id/img" والخاصيتين layout_below و layout_alignParentRight المستخدمتين مع العنصر Button وذلك لوضعه أسفل العنصر EditText و بمحاذاة الشاشة من جهة اليمين: android:layout_below="@+id/edttxt" android:layout_alignParentRight="true" بهذا نكون قد وصلنا إلى نهاية ثاني دروسنا من سلسلة أندرويد للمبتدئين، في انتظار تجربتكم وآرائكم.
  4. شجرة مسار الصفحة (سنقوم بتسميتها لاحقًا بـالمسار) هي عبارة عن التفريعة الكاملة للصفحة بدءًا من الصفحة ‏الرئيسة للموقع، ثم التصنيف الرئيس للصفحة وأي تصنيفات فرعية لها، ومن ثم اسم الصفحة الحالية، مع وجود ‏روابط للبندين الأولين في الشجرة دون الأخير لأنه مفتوح أمامك حاليًا في المسار.‏ هناك تقنيات وخصائص في الإصدارات القديمة لـCSS‏ لا تزال تستخدم بكثرة في الوقت الحالي بالرغم من توفر ‏الإصدارات الحديثة ‏CSS3‎‏. سنقوم في هذا الدرس بإنشاء مسار ذي تصميم مُسطح، بدون الحاجة إلى وضع خلفية ‏صورة له.‏ ستكون روابط المسار مُنسَّقَة بأشكال الشارات لدعم فكرة التنقل الهرمي في المحتوى. وقد اعتدنا على وضع صورة ‏خلفية من نوع ‏PNG‏ للمسار مشابهة لأشكال الشارات، لكننا اليوم بفضل تقنيات الحدّ الذكي ‏clever border‏ سنقوم ‏بإنشاء كامل الخلفية باستخدام CSS‏ فقط.‏ يمكم معاينة مثال حي لما سيكون عليه المثال بعد نهاية الدرس. سنبدأ مباشرة بعمل روابط التنقل في المسار على هيئة ‏‎ul‎‏. سيظهر كل رابط في المسار كـ ‏li‏ ضمن نقطة ‏عنصر في المسار: ‏<div id="crumbs"> <ul> <li><a href="#">Breadcrumb</a></li> </ul> </div> نستهلّ كتابة كود CSS‏ بإنشاء كل نقطة عنصر كمربع أزرق اللون. ونقوم بتوسيط النص في المسافة المخصصة ‏للرابط متساوية على الجانبين. نستخدم ‏position: relative‎‏ لضبط خاصية التموضع للعناصر بحيث تكون ‏مرتبطة بـ ‏ul‏ الأب: #crumbs ul li a { display: block; float: left; height: 50px; background: #3498db; text-align: center; padding: 30px 40px 0 40px; position: relative; margin: 0 10px 0 0; font-size: 20px; text-decoration: none; color: #fff; } نقوم بإعادة تشكيل تأثيرات شكل الشارة في CSS‏ كي تصبح مثل خلفية صورة. استخدم مُحدِّد ‏‎:after‎‏ لإنشاء ‏عنصر إضافي يكون تنسيقه خاصًا به دون غيره. تشكّل المُثلث من خلال تطبيق العديد من حدود الـCSS، فكما ترى ‏في الصورة السابقة أن المثلث يمكن إنشاؤه بتطبيق الحدّ العلوي والسفلي بحيث تتقاطع بهذا الشكل. لعلك لاحظت ‏وجود المساحات الحمراء، تركتُها لإيضاح الفكرة فقط، حيث سنقوم فيما بعد بتحويل اللون إلى شفاف حتى يظهر ‏المثلث الأزرق جليًا. سنقوم باستخدام تأثيرات الحدود هذه في مكان آخر من خلال التموضع الحر.‏ #crumbs ul li a:after { content: ""; border-top: 40px solid red; border-bottom: 40px solid red; border-left: 40px solid blue; position: absolute; right: -40px; top: 0; } سينتج لدينا المثلث المطلوب بعد تطبيقنا لتأثيرات الحدود مع الألوان المُحَدَّدَة لكل حدّ، وبهذا يتكون لدينا شكل الشارة ‏لكل رابط نضعه ضمن المسار:‎ ‎ border-top: 40px solid transparent‏;‏ border-bottom: 40px solid transparent‏;‏ border-left: 40px solid #3498db‏;‏ باستخدامنا لنفس المبدأ السابق، نستطيع تطبيق الخطوات لينتُجَ لدينا شكل جديد على يسار المسار. سنقوم هذه المرة ‏بوضع لون الحدّ بنفس لون خلفية الصفحة حتى تختفي أجزاء من لون خلفية الرابط، ويظهر كامل الشكل مرتبًا: #crumbs ul li a:before { content: ""; border-top: 40px solid transparent; border-bottom: 40px solid transparent; border-left: 40px solid #d4f2ff; position: absolute; left: 0; top: 0; } نلاحظ أن المثلث المضاف حديثًا يؤثر على مظهر النص الخاص بوصف الرابط الذي قبله، ولكن يمكننا تدارك الأمر ‏بتعديل بسيط في الـ‎padding‎‏: padding: 30px 40px 0 80px‏;‏ كلما أضفت روابط جديدة للمسار، فإنه يزداد حجمه طولاً. وكل رابط منها مُنسق بشكل الشارة، بفضل تأثيرات حد ‏المثلث في الـCSS‏ واللمسة الجمالية للـ‏right margin‏.‏ <div id="crumbs"> <ul> <li><a href="#1">One</a></li> <li><a href="#2">Two</a></li> <li><a href="#3">Three</a></li> <li><a href="#4">Four</a></li> <li><a href="#5">Five</a></li> </ul> </div> يمكن تنسيق قائمة المسار بشيء أجمل، قم بإزالة تأثيرات المثلث عن العنصر الأول والأخير من خلال المُحدِّد ‏‏‏‎:first-child ‎‏ و ‏‎:last-child‏ باستخدام ‏‎border-radius‏، تلاحظ تحول زوايا العنصرين الأول والأخير ‏إلى زوايا مستديرة.‏ #crumbs ul li:first-child a { border-top-left-radius: 10px; border-bottom-left-radius: 10px; } #crumbs ul li:first-child a:before { display: none; } #crumbs ul li:last-child a { padding-right: 80px; border-top-right-radius: 10px; border-bottom-right-radius: 10px; } #crumbs ul li:last-child a:after { display: none; } كل ما تبقى علينا هو تطبيق تأثيرات الـ‏hover‏ على الروابط. لا تنس تغيير لون الحد اليسار ‏border-left-‎color‏ في حدث التنسيق ‏hover‏ الخاص بتأثيرات المثلث، حتى تضمن أن تتغير الألوان مع مرور الماوس على ‏روابط المسار.‏ ‏#‏crumbs ul li a:hover‏ ‏{ background: #fa5ba5‎‏;‏ ‏}‏ ‏#‏crumbs ul li a:hover:after‏ { border-left-color: #fa5ba5‎‏;‏ ‏}‏ يمكن معاينة المثال من هنا، أو تحميل الشفرة المصدرية لكامل المثال من على حساب أكاديمية حسوب على github. ترجمة -وبتصرف- للمقال How To Create Flat Style Breadcrumb Links with CSS لصاحبه Iggy.
  5. لكل شيء تجربة مُستخدم. مهمّتنا ليست خلق تجربة المُستخدم، بل تحسينها. ولكن ما معنى "تحسين" تجربة المُستخدم؟ هذا هو الدرس الأول من سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience (هذا الدرس) فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم يشيع الاعتقاد أن تجربة المُستخدم الجيدة هي تحقيق سعادة المستخدمين؛ وهذا غير دقيق! لو كانت السعادة غايتنا لاكتفينا بصور القطط و عبارات المديح العشوائية وعدنا إلى بيوتنا! للأسف لن يكون مديرك في العمل راضيًا (مع أن الفكرة ليست سيّئة!) هدف مصمّمي تجربة المُستخدم هو الوصول إلى كفاءة المستخدم. تجربة المستخدم ليست سوى قمّة جبل الجليد: يعتقد كثير من الناس أن كلمة UX تعني تجربة المستخدم، ولكنها بالأحرى تعني عملية تصميم تجربة المستخدم. تجربة كل مستخدم على حدة ليست سوى رأيه الشّخصي عن موقعك أو تطبيقك. صحيح أن رأي المستخدم مهم (أحيانًا) ولكن على عاتق مصممي التجربة مسؤوليات أكبر من ذلك. تصميم تجربة المُستخدم: يشمل تصميم تجربة المُستخدم (UXD اختصارًا) إجراءات مشابهة جدًّا لأصول البحث العلمي، فنحن نبدأ بفهم طبيعة المستخدمين، ثم التفكير بتلبية حاجاتهم (وحاجات المشروع)، ثم نبني هذه الحلول ونقيس أداءها على أرض الواقع. تابع معنا لتتعلم الكثير عن تجربة المُستخدم، أو تابع صور القطط إن لم تكن مهتمًّا! ركنا تجربة المستخدم الأساسيان ينبغي عليك عندما تبدأ مشروع تجربة مُستخدم جديدًأ وقبل أن تصمّم أيّ شيء، أن تفهم أهدافك؛ هدفين اثنين على وجه الدقّة. كل شيء تفعله قائم على هذين الهدفين ولا شيء أهمّ منهما لنجاح عملك كمصمّم تجربة المُستخدم: أهداف المستخدمين، وأهداف المشروع. أهداف المستخدمين يريد المستخدم شيئًا ما منك، فهو إنسان، وللإنسان دومًا حاجات. سواء كانت هذه الحاجات هادفة أو لا. أهداف المشروع لكل مؤسسة هدف من وراء الموقع أو التطبيق الذي تبنيه، عادة يكون الهدف المال، ويمكن أن يكون الدعاية للشركة، أو جذب المستخدمين للمجتمع… إلخ. تحديد نوع هذا الهدف أمر مهمّ. فلو كان الهدف عرض إعلانات أكثر، فإنّ سياسة تجربة المُستخدم ستكون مختلفة كلّ الاختلاف عمّا إذا كان الهدف هو بيع المنتجات أو الترويج للمشروع في الإعلام الاجتماعيّ. تُسمّى هذه الأمور "القياسات" (metrics) أو "مؤشرات الأداء الأساسيّة" (KPIs) كما يحلو لرجال الأعمال تسميتها. التنسيق بين الهدفين حسن التنسيق بين الهدفين السابقين هو الامتحان الحقيقي لمصمم تجربة المُستخدم، والمقصود هو كيف تجعل غاية المشروع تتحقّق عندما يحصل المستخدم على ما يريد (وليس العكس!). يجني YouTube أرباحه من الإعلانات، ويريد مستخدموه مشاهدة مقاطع فيديو جيدة، ولذلك فإن وضع الإعلانات في المقطع نفسه (أو في الصفحة نفسها) أمر منطقيّ. ولكن الأمر الأهمّ هو أن تسهيل البحث عن مقاطع الفيديو وإيجاد المقاطع المشابهة سيؤدي إلى زيادة ما يشاهده المستخدم، وهذا بدوره يزيد في أرباح YouTube. لو لم يكن الهدفان مُنسّقين، لاستطاع المستخدمون تلبية حاجتهم دون إفادة المشروع (مستخدمون كثر ولكن بلا نجاح) أو أن الأمر على العكس، أي أن المستخدمين لا يستطيعون تلبية حاجتهم (لا مستخدمين ولا نجاح). لو فرض YouTube إعلانًا مدّته دقيقة على كل نصف دقيقة تشاهدها، لانتهى به الأمر سريعًا نهاية عسيرة، ولكنّ إعلانًا مدّته بضع ثوانٍ هو ثمن قليل تدقعه مقابل مشاهدة دب الباندا وهو يعطس… صحيح؟ المكونات الخمسة لتجربة المستخدم في عملية تصميم تجربة المُستخدم، على المصمم أن يحفظ في ذهنه خمسة أمور طيلة العملية. المكوّنات الخمسة لتجربة المُستخدم: الجانب النفسي، وقابليّة الاستخدام، والتصميم، والجمل الترويجية، والتحليل. بإمكاننا أن نفرد في الحديث سلسلة طويلة لكلّ من هذه الجوانب، ولكنّنا سنبُسِّط الأمور بعض الشيء، فهذه السلسلة موجزة، وليس الغرض منها التعمّق في التفاصيل. أولا: الجانب النفسي عقل المستخدم معقّد، وأنت تعرف ذلك. يتعامل مصمّم تجربة المُستخدم مع ذهنيّة غير موضوعيّة تتحكّم بها المشاعر كثيرًا؛ ولهذه الذّهنيّة تأثير سلبيّ أو إيجابيّ على نتائجك، وعلاوةً على ذلك ينبغي على المصمم تجاهل جانبه النفسيّ الخاص أحيانًا، وهذا أمر عسير. اسأل نفسك: ما الذي يدفع المستخدم ليزور خدمتي في الأساس؟ ما شعوره عندما يفعل ذلك؟ كم من الجهد يبذله ليصل إلى ما يريد؟ ما العادات الّتي تنشأ مع تكرار ذلك مرارًا؟ ما الذي يتوقّعه عندما ينقر على هذا؟ هل تفترض أنّه يعلم شيء وهو لم يتعلّمه بعد؟ هل يريد أن يكرّر هذا الأمر؟ كم مرّة؟ هل تفكّر بحاجات المستخدم ورغباته، أم بحاجاتك ورغباتك؟ كيف تكافئ التّصرّف السّليم؟ ثانيا: قابلية الاستخدام صحيح أن الجانب النفسيّ للمستخدم أمر متعلّق ببواطنه، ولكن قابليّة الاستخدام على العكس من ذلك، وباستطاعتك ملاحظة حيرة المستخدم. أحيانًا تكون صعوبة تنفيذ شيء ما أمرًا ممتعًا (كما في الألعاب)، ولكن الغالب لكل ما سوى الألعاب أن تكون سهولة الإنجاز هي ما نريده. اسأل نفسك: هل يستطيع المستخدم إنجاز العمل المطلوب بأقل قدر من الإدخال؟ هل باستطاعتنا تجنيب المستخدم الوقوع في الخطأ؟ (الجواب: نعم!) هل الأمر واضح ومباشر، أم أنّه غامض؟ هل الأمر سهل إيجاده (وهذا أمر جيّد)، أم صعب تفويته (أفضل)، أم متوقّع دون تفكير (الأفضل)؟ هل يتلاءم تصميمك مع افتراضات المستخدم أم يعاكسها؟ هل وفّرت كل ما ينبغي على المستخدم معرفته؟ هل يمكن إنجاز الأمر نفسه بالجودة نفسها ولكن بطريقة مألوفة أكثر؟ هل تبني قراراتك على منطقك أنت؟ أم على بديهة المستخدم؟ كيف تتأكد؟ إن لم يقرأ المستخدم النصوص المكتوبة بخطّ صغير، هل يبقى الأمر مفهومًا؟ هل يمكن إنجازه؟ ثالثا: التصميم تعريفك لكلمة "التصميم" كمصمم تجربة المُستخدم مختلف بعض الشيء عن المفهوم الفنّي الذي يعرفه المصمّمون. لا يهمّ إن كانت الكلمة تعجبك أم لا. التصميم في تجربة المُستخدم يعني كيف تسير الأمور، وهو شيء يمكن إثباته؛ ولا علاقة له بالأسلوب. اسأل نفسك: هل يعتقد المستخدم أن المنتج جميل؟ هل يثقون فيه فورًا؟ هل يوصل المنتج الهدف والوظيفة دون كلمات؟ هل يمثّل العلامة التجارية؟ هل تنسجم مكوّناته معًا؟ هل يقود التصميم عيني المستخدم إلى المواضع الصحيحة؟ كيف تتأكّد؟ هل تساعد الألوان والأشكال والخطوط المستخدم في إيجاد ما يريده وتزيد من قابلية مُستخدم التفاصيل؟ هل تبدو العناصر الّتي يمكن النقر عليه مختلفة عن تلك الّتي لا يمكن النقر عليها؟ رابعا: الإنشاء/النصوص Copywriting هناك فرق هائل بين الإنشاء الخاص بالعلامة التجارية والإنشاء الخاص بقابليّة الاستخدام. فالأولى تعزّز صورة الشركة، والثانية هدفها إنجاز الأمور بأسرع وأبسط ما يمكن. اسأل نفسك: هل تبدو النّصوص واثقة وتُعلِم المستخدم بما عليه فعله؟ هل تحثّ المستخدم على إتمام هدفه؟ هل هذا ما تريده؟ هل أكبر النصوص هي أهمّها؟ إن كان الجواب لا، فلماذا؟ هل تُعلّم المستخدم أم تفترض أنّه يعلم؟ هل هي واضحة ومباشرة وبسيطة وفعّالة؟ خامسا: التحليل التحليل هو نقطة ضعف معظم المصمّمين في رأيي، ولكن يمكن إصلاح هذا الخلل. التحليل هو الفارق الرئيسي بين تجربة المُستخدم وأنواع التصميم الأخرى، وفهمه يُعلي من قيمتك. وإتقانه يعني حرفيًّا دخلًا أعلى. فاسأل نفسك إذًا: هل تستخدم البيانات لإثبات صحّة تصميم، أو الوصول إلى التصميم الصّحيح؟ هل تبحث عن آراء غير موضوعيّة أم حقائق موضوعيّة؟ هل جمعت المعلومات الّتي تعطيك الإجابات المطلوبة؟ هل تعرف لم يفعل المستخدمون ما يفعلونه؟ أمّ أنك تفسّر سلوكهم فقط؟ هل تبحث عن أرقام مجرّدة؟ أم تهدف إلى إدخال تحسينات بناء عليها؟ كيف ستقيس شيئًا ما؟ هل تقيس الجوانب المطلوبة فعلًا؟ هل تبحث عن النتائج السيّئة أيضًا؟ لم لا؟ كيف تطبّق هذا التحليل لتحسين المنتج؟ ترجمة وبتصرّف لكل من المقالات التالية للكاتب Joel Marsh: What is UX User Goals & Business Goals The 5 Main Ingredients of UX حقوق الصورة البارزة: Designed by Freepik.
  6. الهاتف المحمول هو جهاز مختلف جدّا عن الحاسوب المكتبي أو الحاسوب المحمول بالتأكيد كلّها تتحدّث نفس اللّغة الثنائية لغة الصفر والواحد ولكن كيفية تفاعلنا معها واستخدامنا لها، بل وحتى علاقتنا مع كل واحد منها تختلف عن الآخر. التصميم للمحمول يتطلّب عقلية مختلفة بعض الشيء عن التصميم للحاسوب. يتطلّب مهارات مختلفة وبالتأكيد إلى قواعد أخرى. لستَ مضطرا إلى التخلّي عمّا توّصل إليه العلم من قواعد تتعلّق بتجربة المستخدم خلال الثلاثين سنة الماضية. لكن الكثير ممّا يصلح على الحاسوب لن يصلح -بكل بساطة- على المحمول. بعد أخذ كلّ ما قيل بعين الاعتبار، نقدّم هنا عشرة مبادئ للتصميم والتّي أعتقد أنّها رئيسية لإعداد تجارب استخدام ناجحة. من الواضح أنّه يجب التفكير في أكثر من هذه المبادئ العشرة ولكن كبداية فهي مفيدة جدّا. 1. ركز على الأهداف الرئيسية للمستخدم إذا كنت كبيرا بالسّن مثلي فربّما تتذكّر جهاز المساعد الرقمي الشخصي لشركة Palm (أنظر الصّورة أدناه). جهاز المساعد الرقمي الشخصي لبالم كان سابقًا للهاتف الذّكي الذي نعرفه اليوم. جهاز المساعد الرقمي الشخصي لبالم، السابق للهاتف الذكي الحديث بالم كان واحدا من روّاد الحوسبة المحمولة، وكانت هذه الشركة تتحكم بشكل جيّد في مبادئ التصاميم المحمولة. لقد ساعدت في كتابة السطور الأولى من قواعد تصميم تجربة المستخدم، وأحثّك على أن تُلقي نظرة على كُتيّب Zen of Palm الرقمي الذي يقدّم توجيهات حول تصميم تطبيقات المساعد الرقمي الشخصي لبالم. ربّما يكون عمر هذا الكُتيّب الآن أكثر من عشر سنوات ولكنّه يحمل نصائح لا تزال قيّمة إلى يومنا هذا. أوضحت بالم وجوب التركيز على أهداف المستخدم الرئيسية. في دليلهم للتصميم شبّهوا الحاسوب بالسيارة الرّباعية الدفع، والمحمول بالسيّارة الرياضية الخفيفة والرشيقة. كالسيّارة الرياضيّة التطبيق أو موقع الويب المحمول يجب أن يكون خفيفا. يجب أن يكون سريعا، رشيقا، مركَّزا ومقتصِرا على الأساسيات. لا يجب أن يُملأ بالميزات غير الضرورية التي لا تتناسب مع صغر الأجهزة المحمولة وتعقيداتها. فظرف استخدامها قصير ولا يسمح بالتركيز ومحاولة الكثير فكّر فيما يرغب المستخدمون بإنجازه وركّز على أهدافهم. مثلا ربّما يحتاج المستخدمون إلى البحث عن فندق محّلي، معرفة ساعة انطلاق القطار القادم أو الإطّلاع على حالة الطقس اليوم. ركّز على الأهداف الرئيسية التي حددتها (من خلال بحث المستخدم) ولا تشتت أفكارك بمحاولة تصميم وإضافة ميزات من المستبعد أن تستخدم على المحمول. التطبيق المحمول يجب أن يكون كالسيّارة الرياضيّة -سريع، رشيق، مُرَكَّزًا ومُقتصِرا على الأساسيات. 2. لا تنقل أفكارا ومبادئ من الحاسوب هل يبدو لك هذا مألوفا؟ "بسرعة، نحتاج [تطبيقا /موقع ويب]، ونحتاجه الآن. خذ تصميمنا الحالي وقم بتشغيله على المحمول. ليس الأمر في غاية الصعوبة؟" حسنا...، الأمر بسيط جدا، تعديل بسيط على CSS، غيّر بعض الأمور هنا وأمورًا أخرى هناك وانتهى كلّ شيء، النتيجة تصميم محمول رديء لأنه لم يأخذ بعين الاعتبار خصوصيّات الأجهزة المحمولة. لا يمكنك ببساطة نقل تصميم موجّهة للحواسيب إلى تصميم محمول، ليس الأمر بهذه السهولة. الفرق شاسع بين الجهازين. التفاعل مع الجهازين مختلف (الفأرة مقابل اللمس) حجم الشاشة مختلف (كبير مقابل صغير) مدّة الاستخدام مختلفة (طويلة مقابل قصير ومكثّف) ظروف الاستخدام مختلفة (داخلي مقابل خارجي وفي الهواء الطلق) والقائمة تطول. إن كنت لا تصدّق قولي بأّن نقل تصميم حاسوبي إلى تصميم محمول ليس بالفكرة المثلى فقط ألقِ نظرة على واجهات حاسوب ميكروسوفت الكفّي الأوّلي وويندوز موبايل. الحاسوب الكفّي، برهنة لما سيحدث في حال نقل تصميم حاسوبيّ إلى تصميمٍ محمول بدلا من محاولة نقل تصميم حاسوبيّ إلى تصميمٍ محمول، سيكون من الأفضل أخذ خطوة إلى الوراء والبحث في كيفية إنجاز تصميم محمول جديد. ماهي الأهداف الرئيسية التّي تحتاج أن تركّز عليها؟ ماهي الميزات التي يمكن حذفها؟ بماذا ستتميّز رحلة مستخدم المحمول عن رحلة مستخدم الحاسوب؟ كيف ستغيّر واجهة المستخدم حتى تدعم اللمس وتتناسب مع شاشة أصغر بكثير؟ أظّن أن الكثير من التصاميم المتجاوبة تُخفق هنا. كان يُفترض بك أن تقوم ببعض التعديلات البسيطة على تصميم موجّه للحواسيب حتى يعمل على المحمول. رغم أن هذا يمكن أن يكون مقبولا في التفاعلات البسيطة كقراءة مقال، لكن هذا غير ممكن في التفاعلات المعقدّة. بدلا من محاولة النقل ببساطة من تصميم مُعدّ للحواسيب إلى تصميم محمول، من الواجب حقّا أن تعيد التفكير في تصميمك الخاص بالحواسيب. لا تحتاج أن تبدأ مجددا من الصفر ولكن من المؤكد أنك ستفكّر في تصميم يأخذ بعين الاعتبار نقاط الاختلاف الرئيسية بين الحاسوب والمحمول. 3. تخلص من الفوضى الفوضى هي أحد أعداء للتصميم الجيّد. الفوضى سيّئة بما فيه الكفاية في الحاسوب أو في موقع الويب ولكن على المحمول هي أسوأ بمئات المرات. نصوص وعناصر غير ضرورية تبعثر واجهة المستخدم، ميزات غير ضرورية تشوّش تجربة المستخدم. من المهّم التخلّص من كلّ ما هو غير ضروري في التصميم المحمول. استخدم الأيقونات بدلا من النصّ، ولكن لا تزال تحتاج إلى الاستعانة بإضافة وصف إن كانت الأيقونة غير واضحة. استخدم الكشف التدريجي progressive disclosure لإظهار عناصر التحكم الرئيسيّة والمحتوى، مع إضافة خيار لعرض المزيد. باختصار، ما يمكن التخلّص منه في التصميم المحمول يجب أن تتخلص منه. في الحقيقة إن كان بإمكانك أن تتخلّص من كامل الواجهة فلتفعل ذلك. هل قرأت مقال قولدن كريشنا المحفّز أفضل واجهة على الإطلاق هي عدم وجود واجهة والكتاب الذي يحمل نفس العنوان لتعرف لماذا تكون أفضل تجارب المستخدم المحمولة غالبا دون تفاعل، أو على الأقل تتطلّب القليل من التفاعل مع الواجهة. 4. قسم المهام إلى أجزاء صغيرة تقسيم المهّام إلى أجزاء صغيرة هو مبدأ ثابت من مبادئ تصميم تجربة المستخدم، كتقسيم مراحل الدّفع (على المتاجر الإلكترونية) Checkout مثلا. هذا المبدأ مهّم بدرجة أكبر في التصاميم المحمولة لأنّك لا تريد أن تعقّد المهّام الصغيرة. لا تكرر نفس المراحل من موقع أو تطبيق على الحواسيب، لأن الأمر سيكون معقّدا على المحمول لكن اجعل كل خطوة بسيطة بقدر الإمكان وتركز على مهّمة واحدة. يمكن أن يزيد هذا في عدد الخطوات ولكن سيحسّن من تجربة المستخدم. 5. اجعل كل شيء أكبر مما هو عليه في الحاسوب أَعلم أن رغبة ما تشدّك إلى تصغير كل شيء في التصميم المحمول لكن قاوم هذه الرغبة قاوم، قاوم، قاوم. عندما تبتكر تصميما محمولا تحتاج إلى تكبير كل شيء عمّا هو عليه في الحاسوب خط أكبر، أزرار أكبر مسافة بين السطور أطول، أزرار تحكم أكبر، كلّ شيء أكبر. لماذا، لأن تصميمك يجب أن يراعي أصحاب الأصابع الخشنة، وضعاف النظر. من الوارد جدًا أن يُستخدم المحمول في الهواء الطلق حيث تكون القراءة أصعب، وارد أيضا أن يستخدموا الهاتف وهم يمشون حيث تكون قراءة الخطّ الصّغير معضلة. سيستخدمون إبهامهم للضغط على الأزرار وعناصر التحكم. ستتفاجأ أيضا حين تعلم أنّ ليس كلّ الهواتف المحمولة تملك شاشات دقيقة كما تعتقد. حاول أن تكون الأزرار وعناصر واجهة المستخدم على الأقّل بحجم 1سم x 1سم (حوالي 44 بكسل X 44 بكسل) فستكون بذلك قابلة للمس إذا لم يكن ذلك ممكنا فيمكنك التنازل قليلا عن الارتفاع لكن لا يجب أن يكون أقل من 44 بكسل X 30 بكسل. أيضا تأكد من أنّ هناك مسافة معقولة بين الأزرار وزِد في كلٍ من حجم النصّ وتباعد الأسطر. وكنقطة انطلاق، اجعل حجم الخطّ الأصلي 16 بكسل وارتفاع الخطّ % 200 . تجد في موقع Vary.com برنامجا صغيرا للتحقق من حجم الخطّ على موقع محمول. 6. قلل من الحاجة إلى الكتابة بحكم أننا لا نملك أقلامًا على أطراف أصابعنا، فإن الكتابة على الهاتف المحمول عملية بطيئة وشاقّة. لذلك من الأفضل التقليل دائما من معدّل الكتابة اللاّزمة في التصميم المحمول. أعط المجال للمستخدمين دائما للّمس بدلا من الكتابة، إذا كان ذلك ممكنا. اجعل الاستمارات قصيرة وبسيطة بقدر الإمكان وأزِل أيّة حقول لا لزوم لها. استخدم الميزات الذّكية كالإكمال التلقائي والبحث عن الرمز البريدي حتى يتسنّى للمستخدمين إدخال الحد الأدنى من المعلومات فقط. احفظ العناوين والتفاصيل الشخصية حتى لا يجد المستخدم عناءًا في إدخالها أكثر من مرّة، وإذا كان من الواجب إدخال المستخدم لكلمة المرور فالأفضل السماح له برؤية كلمة المرور التّي قام بإدخالها من خلال توفير خيار إظهار كلمة المرور. الكتابة بطيئة وشاقّة أثناء الحركة، حتى مع وجود القلم، لذلك تجنّب الحاجة إلى كتابة حيثما كان ذلك ممكنا 7. موضع اليدين يجب أن يؤثر على مكان عناصر التحكم إذا كنت تقرأ هذا المقال على جهازك المحمول فلاحظ كيف تمسكه، إذا كنت لا تقرأه على جهازك المحمول ولكن لديك جهاز بجانبك تناوله ولاحظ مكان أصابعك وإبهامك، أين يمكنك اللمس بسهولة، أي الأجزاء يمكنك الوصول إليها لو كنت تمسك المحمول بيد واحدة، إذا كان كلّ ذلك فهذا يعني أنّ لديك يدان كبيرتان جدّا أو هاتفا ضئيل الحجم. قبضة اليد ومكانها يجب أن يؤثرا على تموضع عناصر التحكم في التصميم المحمول. الصورة أدناه (المأخوذة من هذا المقال). تبيّن أيُّ المساحات على الشاشة الأسهل للّمس على محمول ذي حجم عادّي. حاول أن تضع الإجراءات الشاسعة في المساحة الخضراء السهلة الوصول، وضع أزرار التعديل والحذف التي لا تريد أن يلمسها المستخدم عن طريق الخطأ في المساحة الحمراء الصعبة الوصول مع هذا يجب دائما طلب تأكيد أو إلغاء أمر الحذف من المستخدم. من المهّم أيضا اختبار التصميم المحمول على بعض الأجهزة المحمولة ومدى ملائمته مع مجموعة من الأشخاص. يجب عليك أن تراقب كيف يتفاعل الأفراد مع تصميمك عندما يستخدمونه على جهازهم المحمول أو جهاز شبيه بجهازهم. مساحات اللمس على المحمول 8. استغل إمكانيات الهاتف الجوّلات الحديثة حقا هي معجزة العصر التكنولوجية. بلا شك هاتفك الذكي يحوي نظام التموضع العالمي الذي يمكنّك من معرفة مكانك في أي بقعة في العالم. ربّما يحوي على مقياس التسارع وجهاز تحديد الاتّجاه. ويحوي أيضا على كاميرا رقمية وميكروفون وهزّاز لمسي. حاول أن تستغّل هذه القدرات عندما يكون الأمر متاحا حتى تحسّن من تجربة المستخدم. مثلا يمكنك استخدام الكاميرا الرقمية لقراءة أرقام البطاقة الائتمانية بصفة آلية، يمكنك استخدام نظام التموضع العالمي للكشف عن المكان المحلّي وضبطه تلقائيا. يمكنك استعمال حركة هز المحمول وحركات أخرى في تصميمك. 9. اختبر، اختبر، اختبر على المحمول ثم اختبر مجددا في الكثير من الأحيان يبدو التصميم المحمول رائعا على شاشة كبيرة ولكن عندما يوضع على المحمول يفقد أكثر من نصف قيمته. لو أردّت أن تأخذ مبدأ رئيسيا واحدا فقط من هذا المقال فسيكون اختبار التصاميم المحمولة على الأجهزة المحمولة. وعندما أقول اختبار لا أعني بذلك اختبار التصميم على جهازك الخاص لترى إن كان يعمل دون أن ينهار. أعني اختبارا مع مستخدمين حقيقيّن على عدّة أجهزة مختلفة. بالطّبع يجب عليك أن تقوم بتجربته بمفردك أوّلا، ولكن فقط عندما تطلب من المستخدمين القيام بمهّام حقيقيّة حينها سترى الأداء الفعلّي لتصميمك. باستخدام آخر وسائل النمذجة المبدئية مثل InVision ،Axure و Proto.io من السهل جدّا اليوم ابتكار نموذج مبدأي تفاعلي محمول. الخدمات المتاحة مثل Usertesting.com و UX recorder و InVision تسمح لك بتسجيل حصص الاختبارات فلا يوجد حقيقة عذر لعدم تجربتها ،وصدق قول القائل "ليس الخبر كالعيان" وعندما نتحدّث عن تجربة المستخدم فلا بدّ أن تكون المعاينة مع اختبار جيّد. 10. ابتكر تجربة سلسة ماهي أوجه الشبه بين فيسبوك، أمازون و سبوتيفاي؟ بغض النظر عن كونها شركات رائدة في السوق، فهي شركات تعرف جيّدًا أهمّية تقديم تجربة سلسة لمستخدميه. خذ تطبيق سبوتيفاي على سبيل المثال يمكنك إعداد قائمة الأغاني على حاسوبك الشخصي وعلى الفور ستكون القائمة متاحة على المحمول. هم يقدّرون أن تصميم التطبيق المحمول أمر هام، ولكن تصميم تجربة سلسة بين الحاسوب الشخصي، الجوّال والحاسب اللوحي بالنسبة للمستخدم أمر لا يقل أهميّة. لا تنعزل بالتفكير في تصميم التطبيق المحمول، يجب الأخذ بعين الاعتبار الصورة كاملة. مع أي محطة من محطات الاستخدام يتناسب استخدام الهاتف الذكي. هل ستبدأ الرحلة على الأرجح بالهاتف الذكي؟ أين تقع نقاط الانتقال المحتملة بين القنوات، من الجوّال إلى الحاسب مثلا؟ حاول أن تصممّ تجربة سلسة بدلا من تصميم تجربة محمولة، تجربة رائعة عندما تُقَدّم بشكل منعزل ولكن فاقدة لقيمتها عندما تنضّم إلى سائر أجزاء الأحجية المكوّنة لتجربة المستخدم الشاملة. ترجمة -وبتصرّف- للمقال 10key mobile UX design principles لصاحبه Neil Turner.
  7. هذا هو الدّرس الأخير في سلسلة تعلّم CSS، ولقد كان اهتمامنا في الدّروس السابقة مُنصبًّا على خصائص CSS وقيمها التي يمكن استخدامها للتأثير في عرض المستند؛ وفي هذا الدّرس سنُعيد النّظر في غايات استخدام CSS وبنية ورقة الأنماط. للتذكير، هذا هو فهرس السلسلة: مدخل إلى أوراق الأنماط المتتالية (CSS). آلية عمل تعليمات CSS داخل المتصفحات. المحددات (Selectors) في CSS. رصف العناصر (Layout) في CSS. كيفية كتابة تعليمات CSS يسهل قراءتها. تنسيق نصوص صفحات الويب باستخدام CSS. التعامل مع الألوان في CSS. إضافة محتوى إلى صفحة ويب باستخدام CSS. الجداول (Tables) في CSS. تعرف على الصناديق (Boxes) في CSS. تنسيق القوائم (Lists) في CSS. التعامل مع أجهزة العرض المختلفة والمطبوعات في CSS (هذا الدرس). أجهزة العرض والمطبوعات تهدف CSS إلى تحديد أسلوب عرض المستندات للمستخدم، ويمكن لعرض المستند أن يكون على عدّة أشكال. مثلاً: أنت تقرأ هذا المستند على جهاز ذي شاشة عرض (في الغالب)، ولكن يمكن أيضًا إسقاطها على شاشة تستهدف جمهورًا كبيرًا، أو يمكن طباعتها؛ ولهذه الوسائط خصائص مختلفة يمكن لـCSS استغلالها للحصول على طريقة عرض للمحتوى تلائم هذا الوسط. مثال يكون للمستند على موقع ويب مجموعة من الروابط الّتي تسمح للانتقال بين صفحات الوقع. يُستخدم المُعرَّف nav-area على العنصر الأب لمساحة التّنقّل في لغة الرّماز (أو يمكن استخدام الوسم <nav> بدل ذلك في الإصدار الخامس من HTML). عندما يُطبع المستند، فإنّ مساحة التّنقل تصبح غير ذات فائدة، ولذا يمكن إزالتها بالقاعدة التّالية: @media print { #nav-area { display: none; } } فيما يلي بعض أنواع الوسائط الشّائعة: الخاصّية تُشير إلى screen شاشة عرض ملوّنة print صفحة مطبوعة projection جهاز إسقاط all كل الوسائط (الخيار المبدئيّ) تفاصيل أكثر هناك طرق أخرى لتحديد نوع الوسط لمجموعة من القواعد. قد تسمح لغة الرماز المُستخدمة في المستند بتعيين نوع الوسط عند ربط ورقة الأنماط بالمستند، فمثلًا في HTML يمكن تحديد الخاصّية media على الوسم link لتحقيق ذلك. ويمكن أيضًا استخدام الأمر ‎@import‎ في CSS الّذي يسمح باستيراد ورقة أنماط من رابطٍ مُعيّن ويسمح أيضًا باشتراط نوع الوسط عند استيراده؛ وهذا يعني أنّه يمكن استخدام ملفات مختلفة لأنواع الوسائط المُختلفة، وهذا مفيد في التنظيم. الطباعة توفّر CSS دعمًا لتنسيق الصّفحات المطبوعة. يمكن استخدام قاعدة تبدأ بـ‎@page ‎ لتعيين مقدار هوامش الصّفحة المطبوعة، كما يمكن تعيين هوامش الجانب الأيمن بصورة مختلفة عن مقابلها الأيسر باستخدام ‎@page:left‎ و‎@page:right ‎. تُستخدم عادة واحدات مناسبة للطّباعة مثل البوصة (in) والنُقطة (pt والّتي تساوي 1 من 72 جزءًا من البوصة)، والسنتيمتر (cm) والميلّيمتر (mm)، كما يمكن استخدام واحدة em الّتي تعتمد على حجم الخطّ، والنّسب المئويّة (%). يمكن التّحكم بموضع فصل المحتوى على صفحتين باستخدام الخواصّ page-break-before و page-break-after و page-break-inside. مثال القاعدة التّالية تعيّن هوامش الصّفحة المطبوعة على كل الجوانب لتساوي بوصة واحدة: @page {margin: 1in;} القاعدة التّالية تُجبر العناوين الرئيسيّة (h1) على أن تكون في رأس الصّفحة دومًا: h1 {page-break-before: always;} تفاصيل أكثر يختلف دعم المتصفّحات لهذه الميّزات، فمتصفّح Firefox يُعيّن قيمًا مبدئيّة للهوامش والترويسات والتّذييلات عند الطّباعة، فلا يمكن توقّع المظهر الّذي ستبدو عليه الصّفحة عند طباعتها بدقّة. واجهة المستخدم لـCSS بعض الخواصّ المُخصّصة للأجهزة الّتي توفّر واجهة استخدام، كشاشات العرض، بحيث تسمح بتغيّر مظهر المستند بصورة ديناميكيّة استجابةً لتفاعل المستخدم مع الواجهة. لا توفّر CSS نوع وسيطٍ خاصّ بالأجهزة ذات واجهات المُستخدم. هناك خمس مُحدِّدات خاصّة: المُحدّد يُحدّد ‏E:hover أي عنصر E تمرّ فوقه الفأرة أو جهاز تأشير آخر ‏E:focus أي عنصر E استحوذ على تركيز لوحة المفاتيح ‏E:active أي عنصر E فعّال (أي يتمّ النقر عليه بالفأرة أو بالإصبع...) ‏E:link أي عنصر E هو رابط لم يزره المستخدم مؤخّرًا. ‏E:visited أي عنصر E هو رابط زاره المُستخدم مؤخّرًا. الخاصّية cursor تسمح بتغيير شكل مؤشّر الفأرة، وفيما يلي بعض الأشكال الشّائعة (مرّر الفأرة فوق كلّ القيمة لترى الشّكل الموافق): المُحدِّد يُحدِّد ‏pointer يُشير إلى رابط ‏wait يُشير إلى أنّ البرنامج لا يستطيع استقبال أي مدخلات لانشغاله ‏progress يُشير إلى أن البرنامج يُنفّذ أمرًا ما، لكنّه ما زال يستطيع استقبال المُدخلات ‏default المبدئيّ (سهم عادةً) الخاصّية outline تُنشئ خطًّا حول العنصر يُستخدم عادةً للإشارة إلى استحواذه على تركيز لوحة المقاتيح، وتكون قيمتها مُشابهة لقيم border، إلّا أنه لا يمكن تعيين قيم مختلفة لكلّ جانب من الخطّ. بعض المزايا الأخرى لواجهات الاستخدام تعتمد على خواص الوسوم (attributes)، فمثلًا يمكن جعل العنصر مُعطّلًا (disabled) أو السّماح بقراءته فقط (read-only) بالخاصّتين disabled و readonly على التّرتيب، ثمّ يمكن استهداف العناصر الّتي تحمل هذه الخواصّ في CSS، كأي خواصّ أخرى، بجعلها ضمن قوسين مُربّعين هكذا: [disabled] و [readonly]. مثال القاعدة التالية تعيّن تنسيق زرّ يتغيرّ استجابة لتفاعل المُستخدم: .green-button { background-color:#cec; color:#black; border:2px outset #cec; } .green-button[disabled] { background-color:#cdc; color:#777; } .green-button:active { border-style: inset; } وهكذا يبدو الزّر في الحالات المختلفة: يُحيط للزّر في الحالة المبدئيّة خطّ داكن عندما يُجعل هذا الزّر هو الزّر المبدئيّ، وخطّ مُنقّط عندما يستحوذ على تركيز لوحة المفاتيح. وقد يكون له تنسيق خاصّ عندما يحطّ فوقه المؤشّر. تدريب: طباعة مستند أنشئ مستند HTML جديدًا، سمّه doc4.html، والصق فيه ما يلي: <!DOCTYPE html> <html> <head> <title>Print sample</title> <link rel="stylesheet" href="style4.css"> </head> <body> <h1>Section A</h1> <p>This is the first section...</p> <h1>Section B</h1> <p>This is the second section...</p> <div id="print-head"> Heading for paged media </div> <div id="print-foot"> Page: </div> </body> </html> أنشئ ورقة أنماط جديدة، سمّها style4.css والصق فيها ما يلي: /*** Print sample ***/ /* defaults for screen */ #print-head, #print-foot { display: none; } /* print only */ @media print { h1 { page-break-before: always; padding-top: 2em; } h1:first-child { page-break-before: avoid; counter-reset: page; } #print-head { display: block; position: fixed; top: 0pt; left:0pt; right: 0pt; font-size: 200%; text-align: center; } #print-foot { display: block; position: fixed; bottom: 0pt; right: 0pt; font-size: 200%; } #print-foot:after { content: counter(page); counter-increment: page; } } /* end print only */ اعرض المستند في متصفّحك. يستخدم المستند تنسيق المتصفّح المبدئيّ. اطبع المستند (أو عاينه قبل الطّباعة)، تجعل ورقة الأنماط كلّ قسم في صفحة مستقلّة، وتُضيف ترويسة وتذييلًا لكل صفحة، وتستخدم رقم الصّفحة في التّذييل (فقط إن كان المتصفّح يدعم العدّادات). تمرين انقل التنسيق الخاصّ بالطّباعة إلى ملفّ CSS مُستقلّ، ثمّ راجع صفحة ‎@import‎ لإيجاد كيفيّة استيراد ورقة أنماط للوسائط المطبوعة ضمن ملفّ style4.css. اجعل العناوين زرقاء عندما يحطّ فوقها مؤشّر الفأرة. شاهد الحل ملف منفصل لتنسيق الطباعة قص السّطور بين /* print only */ و/* end print only */ والصقها ضمن ملفّ سمّه style4_print.css، ثمّ أضف السّطر التّالي لبداية الملفّ style4.css: @import url("style4_print.css") print; لون العناوين عندما يحط المؤشر فوقها القاعدة التّالية تُحقّق التأثير المطلوب: h1:hover { color: blue; } ترجمة بتصرّف للدرس Media من سلسلة Getting started with CSS على شبكة مطوّري Mozilla.
  8. سيفاجئك العملاء بطرق استخدام منتجك مبتكرة. وهذا لا يأتي من دراسة وتفكّر من جانبهم وإنّما نتيجة لجعلهم منتجك يتكيّف مع احتياجاتهم. يقول Peter Drucker قولته المشهورة: "نادرًا ما يشتري العميل ما تظنّ الشركة أنّها تبيعه"؛ وهذه إشارة إلى أنّه لكي تطوّر منتجًا ما يجب عليك أوّلًا أن تدرس وتفهم الغرض الذي سيُستخدم من أجله. لنوضّح الأمر بمثال: بعد أن أطلقنا Intercom بفترة قصيرة قمنا بإضافة ميزة الخارطة لنتمكّن من معرفة أماكن عملائنا حول العالم. كانت هذه الميزة من النوع الكلاسيكي وكانت رائعة جدًّا لكنّنا لا نعرف لماذا. ولقد استطعنا رؤية كم أصبحت هذه الخارطة شعبيّة من خلال عدد الأشخاص الذين يستخدمونها. لكن التسويق للخارطة كميزة كان صعبًا، لأنّه كان من الصعب معرفة سبب استخدامها. هل هو معرفة المكان الذي يأتي منه أغلب العملاء؟ كلّا، العديد من المُنتجات تفعل ذلك.هل هو رؤية العملاء من مدينة معيّنة؟ كلّا، فقوائم المستخدمين تفعل ذلك بشكل أفضل بكثير.هل هو معرفة عدد مستخدمي المنتج في بلد معيّن؟ كلّا، فقوائم المستخدمين تفعل ذلك بشكل أفضل بكثير أيضًا.إذًا ما هو الغرض من الخارطة بغضّ النظر عن كونها مثيرة للإعجاب؟ لقّد فكّرنا بثلاث طرق استُخدمت فيها هذه الخاصيّة: هنالك أشخاص يفضّلون استعراضها في المعارض التجارية والمؤتمرات (انظر إلى الحاسوب الشخصي): وأشخاص يفضّلون استعراضها على تويتر: وآخرون يفضّلون استعراضها أمام المستثمرين: إذًا ما تفعله الخارطة هو أنّها تبدو رائعة وتثير إعجاب العملاء؛ هذا كل ما تفعله. التحسين على أساس الاستخداملو أردنا تحسين الخارطة قبل معرفة الغرض الذي تُستخدم من أجله لحاولنا إنشاء خارطة أفضل. فيما يلي بعض الأمور التي قد نركّز عليها: الدّقة الجغرافية.المجموعات الجغرافيّة.حدود أفضل للدولة أو المدينة.خاصية السحب لإنشاء مناطق regions.تحسينات خرائطيّة أخرى متنوّعة.قد تستغرق منّا هذه التحسينات عدّة أسابيع أو أشهر، وفي النهاية تؤدّي إلى منتج أسوء؛ لأنّ العميل لم يكن يشتري ما كنّا نظن أنّنا نبيعه. فالخارطة أصبحت عبارة عن قطعة للعرض وليست خارطة. ما الذي يجعل الخارطة تقوم بتلك المهمّة بشكل أفضل؟ أولًا وقبل كل شيء، خارطة مصمّمة لتبدو جيّدة.خارطة تقوم بإخفاء البيانات الحسّاسة بشكل تلقائي مما يجعلها قابلة للمشاركة.خارطة من السهل على العملاء مشاركتها.وهذا بالضبط ما قمنا بعمله. لقد قمنا بعرض على عملائنا فرصة مشاركة خارطة متحرّكة وجميلة، وزوّدناهم بعنوان URL فريد من نوعه وقابل للمشاركة: خارطة أسوء تقوم بعمل أفضلعندما تقوم بالتركيز على الكيفيّة التي تُستخدم فيها الميزة، متجاهلًا فئة أو نوع الميزة، ستتعلم كيف تقوم بتحسينها بسرعة، وهذه التحسينات سيتردد صداها على الفور. بإمكانك مشاهدة المزيد من ردود أفعال العملاء تجاه الخارطة القابلة للمشاركة هنا. حقوق التّصميم عائدة لـHongyuan وحقوق تطوير الميزة عائدة لـEoin وPatrick. ترجمة -وبتصرّف-للمقال This is not a map لصاحبه: Des Traynor. حقوق الصورة البارزة: Designed by Freepik.
  9. سيكون هذا هو الدرس الأول من أصل درسين سنقوم من خلالهما بتصميم وتكويد واجهة موقع لغابة افتراضية اسمها "Pinewood Forest". سنستخدم في تصميم هذه الواجهة العديد من textures وخليط من اللونين الأزرق والرمادي. ستكون النتيجة النهائية كما في الصورة التالية: سيكون الهدف من هذا الموقع هو تعريف الزوار بهذه الغابة وما تقدمه وإعلامهم بالأحداث القادمة، وستحتوي خلفية الموقع على صورة كبيرة ثابتة لتسمح بتمرير (scroll) محتوى الموقع فوقها، كما سيتم تقسيم محتوى الموقع إلى عدة أجزاء رئيسية مع استخدام بعض الصور الفوتوغرافية لجذب انتباه الزائر. سنقوم في هذا الدرس بتصميم الواجهة الرئيسية للموقع باستخدام برنامج الفوتوشوب، أمّا في الدرس الثاني فسنقوم بتكويد التصميم باستخدام لغتي HTML وCSS. سنبدأ أولًا بإنشاء مستند جديد، وبما أنّ التصميم سيحتوي على صورة خلفية كبيرة فإننا سنحتاج إلى إنشاء مستند يستطيع استيعاب الشاشات ذات الدّقة والأبعاد الكبيرة (24 أو 27 انش). باستخدام أداة marquee قم برسم منطقة في منتصف الصفحة بعرض 960px وقم بوضع guides على طرفي ما قمت برسمه. ستكون هذه المنطقة هي المنطقة التي سوف تحتوي على المحتوى الرئيسي للموقع. قم بتحميل صورة ذات أبعاد كبيرة يمكنها تغطية معظم الصفحة (يمكنك تحميل الصورة التي استعملتها في هذا الدرس من هنا). قم الآن بإدراج هذه الصورة في المستند وقم بتعديل أبعادها لتتناسب مع الشاشات الكبيرة (لنقل 1920px) واجعل هناك هامش بسيط على كلا الجانبين، ثم ضع guide أسفل الصورة ليصبح كل شيء كما في الصورة التالية: قم الآن بإضافة Layer Mask للصورة واستعمل بعضًا من الفرش المائية (Watercolor brushes) لإبهات الأطراف. قم بتخفيف شفافية (opacity) الفُرش لإنشاء مظهر أجمل لتصبح الصورة وكأنها متداخلة مع الخلفية: قم باستعمال خطّي League Gothic وClarendon لإنشاء الشّعار الخاص بالموقع وقم بوضعه في منتصف الصفحة. قم بتعديل حجم الخطوط حتى يكون التركيز أكبر على كلمة "Pinewood" وقم بمحاذاة الكلمتين عن طريق زيادة المسافة بين الحروف (the tracking) في كلمة "forest". سيكون الشعار مشابهًا للصورة: قم بإضافة تأثيري Drop Shadow وGradient Overlay لكلمة "Pinewood". سوف يضفي كلا التأثيرين نوعًا من العمق على الشعار وسيجعلانه بارزًا أكثر في الصفحة. حاول أن تجعل التباين (contrast) قليل بين تأثير الـgradient وشفافية الظل (shadow) حتى يظهر بشكل أفضل: قم بإضافة/كتابة عناصر القائمة الرئيسية وقم بتوزيعاها على التساوي على كلا جانبي الشّعار. يمكنك استعمال خط Georgia كخط بديل عن Clarendon الذي استعملناه في الشّعار. يمكن تنفيذ التباعد بين حروف الكلمات (tracking) باستعمال الخاصية letter-spacing الموجودة في لغة CSS وهو ما سنفعله لاحقًا في الدرس القادم. قم برسم مستطيل كبير بين الـguides ذات العرض 960px التي وضعناها في خطوة سابقة وقم بملء هذا المستطيل بلون رمادي فاتح كاللون المستعمل في تأثير gradient الخاص بالشّعار: قم بإضافة Noise خفيف عن طريق الذهاب إلى: Filter > Noise > Add Noise ولتكن القيمة 2.5% لإعطاء نوع من الجمال الحسي على منطقة المحتوى في الموقع. اضغط على زر ctrl في لوحة المفاتيح وأبق يدك على هذا الزر وبعدها اضغط بزر الفأرة الأيسر على الصورة المصغرة (thumbnail) للطّبقة (layer) التي أنشأناها في الخطوة السابقة. ستلاحظ أنه تم اختيار المنطقة الخاصة بتلك الطبقة. قم الآن بالضغط بزر الفأرة الأيمن على ما تمّ اختياره واختر Transform Selection ثم قم بتقليص الطول والعرض بنسبة 40px واملأ تلك المنطقة باللون الأبيض، بعدها قم بإضافة تأثير Drop Shadow على تلك الطبقة: قم بتحميل فرش paint roller واستعملها في جعل الحواف الرمادية خشنة نوعًا ما وذلك عن طريق الرسم على قناع الطبقة (layer mask). فعّل التحديد (selection) الخاص بالمنطقة البيضاء واملأها باللون الأسود فوق قناع الطبقة (layer mask) لجعلها شفافة ثم بعد ذلك قم بإنقاص شفافية الحواف الرمادية إلى 60% حتى تظهر الخلفية التي وراءها: قم بتحميل صورة ما لوضعها كخلفية للمنطقة البارزة (main feature area) في الصفحة، ثم باستخدام أداة marquee selection قم برسم مستطيل كما ها موضح في الصورة أعلاه، بعد ذلك اضغط بزر الفأرة الأيمن على تلك المنطقة واختر Select Inverse ثم اضغط على زر delete من لوحة المفاتيح: يمكنك ملاحظة أن ألوان الصورة لا تتناسب كثيرًا مع الموقع، لذلك سنقوم ببعض التعديلات باستخدام أداة Curves. قم بتعديل قنوات اللونين الأخضر والأحمر على شكل يشبه حرف S واللون الأزرق على شكل حرف S ولكن معكوس كما هو موضح في الصورة: قم بتقليل تشبّع (saturation) الصورة لتخفيف حدّة الألوان. ستظهر ألوان هذه الصورة بعد التعديلات بشكل يتلائم مع ألوان التصميم: استعمل واحدة من فرش paint roller التي قمت بتحميلها سابقًا واستعملها لرسم خلفية لوضع بعض النصوص فوقها كما تظهر الصورة: قم بتعديل حجم وتباعد الحروف الخاصة بنص العنوان لإضفاء بعض الجمال، بعد ذلك اجعل حجم الخط للفقرة 14px: عليك الآن باستعمال نفس التأثيرات المستعملة في الشّعار (Gradient Overlay و Drop Shadow) وتطبيقها على النص الذي كتبته للتو: سنحتاج الآن إلى إضافة زر call to action لإخبار الزّوار إلى أين يمكنهم ان يتوجهوا بعد تصفحهم للصفحة الرئيسية. لفعل ذلك قم برسم مستطيل بحجم مناسب واملأه بلون أزرق متوافق مع اللون العام للموقع، ثم قم بإضافة فلتر noise خفيف لتخشين الزر قليلًا. بعد ذلك استعمل أداة المسح (Eraser tool) واختر pencil بعرض 1px وقم بمسح جزء من كل طرف من الأطراف الأربعة للزر. أنظر الصورة التالية: قم بإضافة تأثير Inner Glow للزر واستعمل لون أزرق أدكن بقليل من لون الزر نفسه. لتكن الإعدادات عبارة عن Normal blending mode وشفافية (opacity) بنسبة 100% وقم بتعديل الأحجام حتى تصبح ملائمة. قم أيضًا بإعطاء الزر إطار(stroke) بحجم 1px باستعمال لون أرزق أفتح بقليل من لون الزر: ويتبقى في الآخر إعطاء نص مُعبّر لوضعه على الزر، وتأكّد أيضًا أن جميع عناصر المنطقة البارزة (feature area) مرتّبة بشكل مناسب وأنيق: قم الآن بتعبئة منطقة المحتوى بالنصوص التي تريدها، واجعل حجم خط العنوان أكبر بقليل واجعله يتمدد على عرض الصفحة بالكامل. افصل النص المتبقي إلى عمودين؛ واحد عريض للمحتوى الرئيسي والآخر بعرض أصغر للمحتوى الجانبي (sidebar). يجب علينا أيضًا أن نميّز الروابط بلون مختلف عن باقي النص، لذلك قم بتلوين بعض الكلمات بلون أزرق واجلعها تحتوي على خط أسفلها (underline). في جزء المحتوى الجانبي (sidebar) سيكون هناك ثلاث مناطق تؤدي إلى مناطق أخرى في الموقع، بحيث تحتوي كل واحدة من هناك المناطق على صورة، عنوان وفقرة قصيرة. قم بإضافة خلفية مزخرفة وذلك باستعمال paint roller brushes وقم بعد ذلك بقص صورة خارطة ووضعها داخلها: استعمل paint roller brushes لإنشاء خلفية أخرى بلون أزرق أدكن بقليل من اللون الذي استعملته في الخطوة السابقة وستكون هذه الخلفية هي المكان الذي سيحتوي على العنوان الخاص بكل منطقة. لا تنسى قص الأجزاء المتداخلة لتتوافق مع طرف الصورة. قم بإضافة عنوان قصير فوق الخلفية التي أنشأناها في الخطوة السابقة وقم بإعطاءه نفس التأثيرات المستعملة في النص الموجود في الـfeature section: قم بوضع كل العناصر التي أنشأناها إلى الآن في مجلد واحد وبعد ذلك قم بنسخ ذلك المجلد مرتين وقم بالتعديل على عناصر كل مجلد بالشكل المناسب. أنظر الصورة: سوف نضيف الآن قسم "Upcoming events" أسفل النص الموجود في العمود الرئيسي. قم باستعمال نفس تنسيقات الخطوط الموجودة في الترويسة وباقي المحتوى الرئيسي، وبعدها قم بإنشاء جزء خاص بالتاريخ واستعمل لون رمادي مشابه للإطار(border): كل حدث سيحتوي على التاريخ، عنوان، بعض المعلومات ووصف بسيط: يمكننا الآن التخطيط لكيفية تحويل هذا التصميم إلى ملف HTML وهذا ما سنفعله في الدرس القادم بإذن الله. وبهذا نكون قد وصلنا إلى نهاية المقال، وأصبح لدينا تصميم جاهز يمكننا تحويله إلى صفحة ثابتة باستعمال لغتي HTML وCSS. ترجمة -وبتصرّف- للمقال Design a Textured Outdoors Website in Photoshop لصاحبه Iggy.
  10. سنقوم اليوم بإذن الله ببناء قائمة أخرى مُطَعَّمة بتأثيرات fancy hover‏. وسأعتمد التصميم ‏المُسطح الشائع مُستخدمًا الألوان الزاهية والأيقونات الرائعة، وأُطبّق تقنيات CSS‏ المتعددة، وبذلك يُصبح هذا الدرسُ ‏مقالةً رائعةً لمصممي الويب.‏ مفهوم القائمةقبل أن نبدأ بأي تنسيقات، سنقوم بإنشاء الهيكل الأساسي للقائمة بـ HTML‏. هناك عناصر جديدة في HTML5‎‏ مثل ‏nav‏ مُتاحة هذه الأيام، حتى أنها تعمل على إنترنت إكسبلورر بمساعدة بعض الإضافات مثل ‏ html5shiv. ‏ <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Flat Nav</title> <link href="style.css" rel="stylesheet" /> <link href='http://fonts.googleapis.com/css?family=Dosis' rel='stylesheet' type='text/css'> </head> <body> <div id="demo"> <nav> <ul> <li> <a href="#"> <span>Home</span> </a> </li> <li> <a href="#"> <span>About</span> </a> </li> <li> <a href="#"> <span>Portfolio</span> </a> </li> <li> <a href="#"> <span>Contact</span> </a> </li> </ul> </nav> </div> </body> </html>يبدأ الكود بتعريف ‏HTML5‎‏ كمرجعية للصفحة عن طريق <doctype html!>‏، عنوان الصفحة ‏‏title‏‏ ورابط ‏ملف CSS‏ ‏الذي سنقوم بإنشائه بعد ذلك ‏‏link>‎>. يأتي بعد ذلك ربط صفحة الويب بخط ‏ ‏Dosis‏‏ من ‏‏‏Google Webfonts‏. تبدأ ‏البُنية الرئيسة في القائمة بعنصر ‏‏nav‏، تكون داخله قائمة ‏ul‏. وكل ‏عنصر داخل ‏ul‏ يحتوي على رابط مرفقًا معه ‏عنصر ‏‎span‎‏ لإظهار تسمية الزر على يمين القائمة عند ‏مرور الماوس عليه.‏ تنسيقات CSSnav ul { list-style: none; overflow: hidden; position: relative; } nav ul li { float: left; margin: 0 20px 0 0; }نبدأ بعمل تنسيق ‏CSS‏ بأن نُزيل رمز النقطة من أمام كل عنصر من عناصر القائمة ‏ul‏، نجعل تلك العناصر ‏‏‏li‏ بجوار بعضها البعض. نضع تعريف التنسيق ‏overflow: hidden‏ للقائمة ‏ul‏ لتظهر تسميات ‏الأزرار متناسقة وغير متداخلة، من ثم وضع التنسيق الخاص بتسميات الأزرار ‏span‏ حتى تكون كلٌ منها ‏متمركزة في مكانها الصحيح على ‏li‏ الخاص بها ضمن القائمة ‏ul‏ الأب. nav ul li a { display: block; width: 120px; height: 120px; background-image: url(icons.png); background-repeat: no-repeat; } nav ul li:nth-child(1) a { background-color: #5bb2fc; background-position: 28px 28px; } nav ul li:nth-child(2) a { background-color: #58ebd3; background-position: 28px -96px; } nav ul li:nth-child(3) a { background-color: #ffa659; background-position: 28px -222px; } nav ul li:nth-child(4) a { background-color: #ff7a85; background-position: 28px -342px; }كل نقطة من نقاط القائمة الأربعة يتم تنسيقها لتظهر كمربع بعد إضافة الطول والعرض لها وهو ‏‎120px‎‏، قابلة ‏للتحول من تنسيق ‏inline‏ إلى تنسيق ‏block‏ باستخدام ‏display: block;‎‏. يتم تصدير جميع الأيقونات ‏من الفوتوشوب في صورة ‏sprite‏ واحدة تحوي خلفيات الصور الأربعة في نفس الملف، لذا فإن ملف ‏‏‏icons.png‏ يُعتبر صورة خلفية لجميع نقاط القائمة باستخدام مُحدِّد ‏nav ul li‏. حيث تقوم بتحديد موقع ‏الخلفية من داخل الملف الواحد حسب تموضعها فيه.‏ يُمكنك إضافة تنسيق مُحدَّد لكل نقطة من النقاط الأربعة على حِدة باستخدام مُحدِّد ‏li :nth-child‏. حيث تستطيع ‏أن تضيف ‏classes‏ لكل عنصر لوحده من عناصر القائمة ‏li‏ حسب رقم هذا العنصر. بالضبط كما حددنا لون ‏الخلفية لكل عنصر منفردًا.‏ nav ul li a span { font: 50px "Dosis", sans-serif; text-transform: uppercase; position: absolute; left: 580px; top: 29px; display: none; }نأتي الآن إلى ضبط موقع تسمية النص لعناصر القائمة، بتطبيق حدث التنسيق ‏on hover‏ لجميع العناصر مرة ‏واحدة، وذلك على ‏span‏ التي أضفناها لكل عنصر من عناصر القائمة. أولاً: نقوم بإضافة خصائص الخط ‏‏‏Dosis‏، وهي حجم الخط، و‏uppercase‏ (تحويل الحروف الصغيرة إلى حروف كبيرة) باستخدام خاصية ‏‏‏text-transform‏. ‏ افتراضيًا، فإن كل تسمية عنصر تتموضع في الزاوية العلوية يسارًا على بلوك عنصر القائمة، ولكننا نريدها أن تكون ‏على يمين القائمة ‏ul‏ خارج إطار العناصر. ببساطة، نضيف خاصية الموضع ‏position: absolute;‎‏ لعمل ‏ذلك. قمنا قبل ذلك بوضع الخاصية ‏position: relative;‎‏ إلى ‏nav ul‏ حتى يكون التموضع الحر مرتبطًا ‏بالقائمة ‏ul‏ (الأب)، عدا عن كونها مرتبطة بالعرض الكامل لشاشة المتصفح.‏ nav ul li a:hover span { display: block; } nav ul li:nth-child(1) a span { color: #5bb2fc; } nav ul li:nth-child(2) a span { color: #58ebd3; } nav ul li:nth-child(3) a span { color: #ffa659; } nav ul li:nth-child(4) a span { color: #ff7a85; }نرى الآن جميع تسميات العناصر ظاهرة فوق بعضها البعض في نفس الوقت، لذا سنقوم بإخفائها باستخدام الخاصية ‏‏‏display:none;‎‏ حتى لا تظهر أي من التسميات إلا حين يمر الماوس فوق عنصرها فقط، بإضافة ‏‏‏display:block;‎‏ إلى حدث التنسيق ‏on hover‏ الخاص بكل عنصر. بقي أن نُعطيَ كلَّ تسمية عنصر لونها ‏الخاص بها والمطابق للون خلفية عنصرها، هذا الأمر يتم في مُحدِّد ‏‎:nth-child‏ لكل عنصر على حدة.‏ النص الكامل لملف CSS‏، بعد أن انتهينا من الخطوات جميعها، سوف يصبح لدينا ملف CSS‏ جاهزًا كما يلي، بإمكانك نسخه من هنا: nav ul { list-style: none; overflow: hidden; position: relative; } nav ul li { float: left; margin: 0 20px 0 0; } nav ul li a { display: block; width: 120px; height: 120px; background-image: url(icons.png); background-repeat: no-repeat; } nav ul li:nth-child(1) a { background-color: #5bb2fc; background-position: 28px 28px; } nav ul li:nth-child(2) a { background-color: #58ebd3; background-position: 28px -96px; } nav ul li:nth-child(3) a { background-color: #ffa659; background-position: 28px -222px; } nav ul li:nth-child(4) a { background-color: #ff7a85; background-position: 28px -342px; } nav ul li a span { font: 50px "Dosis", sans-serif; text-transform: uppercase; position: absolute; left: 580px; top: 29px; display: none; } nav ul li a:hover span { display: block; } nav ul li:nth-child(1) a span { color: #5bb2fc; } nav ul li:nth-child(2) a span { color: #58ebd3; } nav ul li:nth-child(3) a span { color: #ffa659; } nav ul li:nth-child(4) a span { color: #ff7a85; } التصميم النهائي لقائمتنا ذات السِمة المسطحة: يمكن معاينة مثال حي عن الدرس، أو تصفح ملفات العمل الخاصة بالدرس. ترجمة وبتصرف للمقال: How To Create a Trendy Flat Style Nav Menu in CSS.
  11. تصميم تجربة المستخدم ليس هو نفس الشيء عندما تقارنه بتصميم واجهات الاستخدام. تجربة المستخدمين تحصل وراء الشاشات وبين الفجوات. كمصممي ويب وواجهات استخدام محمولة فإنّ عملنا يتركّز في بناء واجهاتٍ أنيقة. واجهاتٍ تكون عناصر أساسية في تجربة المستخدم. واجهات تسمح للمستخدمين بأن يحصلوا على أجوبة لأسئلتهم أو إكمال مهام أساسية بالنسبة لهم. عملنا هو أن نساعدهم في تحقيق أهدافهم. لكن إيّاك أن تخطئ وتعتقد أنّ هذه التفاعلات هي كلّ شيءٍ عن تجربة المستخدم. تجربة المستخدمين الرائعة تحصل دومًا خلف الشاشات وفي الفجوات. الفجوات التي تقع بين القنوات، الأجهزة وشركات الأعمال. تحصل تلك التجربة دومًا عندما تقوم المنظّمات بالاهتمام بالتفاصيل الدقيقة لتفاعلاتها مع العملاء. بسبب أنّ تصميم تجربة المستخدم يتعلّق بأكثر من أمر فإنّه من الصعب وصفه عادةً. لذا عوضًا عن ذلك، دعني أعطيك بعض الأمثلة على تفاعلاتٍ تساعد على إنشاء تجربة أفضل للمستخدمين. تفادي إزعاج دعم الهواتفتطبيق Barclays للهواتف الذكية مثال جيّد على تصميم جيّد لتجربة المستخدم. يمتلك التطبيق بذات نفسه واجهة جميلة. ولكنّ هذا ليس هو السبب وراء تجربة المستخدم الجيّدة الخاصّة به. يمتلك Barclays تطبيقًا ممتازًا للهاتف المحمول، ولكن السحر الحقيقي يحصل عندما تحاول الاتصال بهم. إذا أردت الاتصال بـBaraclays فإنّه يمكنك القيام بذلك عبر التطبيق. يسمح لك هذا بتخطّي الحاجة إلى الاستيثاق عبر الهاتف لأنّك قمتَ بهذا بالفعل عندما قمتَ بتسجيل الدخول إلى التطبيق. هذا مثال رائع على تجربة مستخدم مميّزة. أنا متأكّد من أنّ جلب نظام الهاتف والتطبيق ليتحدثا مع بعضهما البعض مباشرةً دون تدخل المستخدم لم يكن أمرًا سهلًا. ولكن بلا شكّ، فإنّه سيساعد المستخدمين على تجنّب الكثير من الإزعاج يوميًا. جعل عمليات الإرجاع أقل صعوبةإذا قمتَ من قبل بإرجاع منتج اشتريته عبر الشبكة فحينها أنت تعرف أنّ الأمر ليس سهلًا دومًا. يجب عليك كتابة طلب إرجاع، الذهاب إلى مكتب البريد والانتظار لأيّام قبل أن يعود المبلغ إلى حسابك الشخصي. وفوق كلّ هذا فهناك ذلك الأمر الذي تقلق حوله دومًا من أنّهم لن يعيدوا لك أموالك. على العكس مما سبق فقد كان لي تجربة حديثة في القيام بعملية إرجاع منتج. بعد أن أخبرت الشركة بأنني أريد إرجاع منتج خاصّ بهم، أخبروني بأنّ المال سيعود مباشرةً إلى حسابي، دون أيّ حاجة لانتظار عودة الطرد إليهم ومن ثمّ التحقق منه مجددًا. عند إرجاع منتج ما فهناك دومًا خوف من ألّا يعيدوا إليك نقودك. هذه فرصة سانحة لتحسين تجربة المستخدم. بعدها سألوني متى أريد أن يتم أخذ الطرد منّي. لم أحتج إلى توضيب الطرد وإغلاقه مجددًا أو تعبئة استمارة إرجاع ولصقها عليه والذهاب إلى البريد أو ما شابه. قام أحدهم فجأة بطرق باب منزلي حاملًا صندوقًا لكي يأخذ المنتج الذي أريد إرجاعه منّي. وضعت المنتج في ذلك الصندوق وأخذه لاحقًا. الآن، هذه تجربة مستخدم مميّزة. مساعدة الزبائن على أن يشعروا بالأمانعملت مرة مع زبون قام ببيع وجبات جاهزة مثلّجة لكبار السنّ. إنّه أمرٌ مثير للقلق بالنسبة لهم عندما يشترون المنتجات عبر الشبكة، ولا يحبّون فكرة أن يقوم شخص غريب بأن يطرق الباب عليهم. للتعامل مع هذه المشكلة، تمّ جعل اسم السائق وهويته واضحين لهم عندما يقومون بطلب عملية توصيل عبر الشبكة. بهذه الطريقة، سيتمكنون من تمييز الشخص الذي سيطرق الباب عليهم لاحقًا ليوصل الطلب إليهم. من الممكن لتجربة مستخدم جيّدة أن تقوم بزيادة أرباحك وأن تميّزك عن منافسيك. ولكنّهم لم يتوقّفوا هناك، بل يقومون بعمل فحص بواسطة الشرطة على جميع السائقين لكي يتمكّن الزبون من التأكّد من أنّ الأمر آمن. كان هذا مكلفًا واستغرق الكثير من الجهد، ولكنّه في المقابل زيادةً ملحوظةً في المبيعات. كان شيئًا لم يتمكّن منافسوهم (مثل Tescos) من تقديمه. الحفاظ على وقت المستخدمبالحديث عن عمليات التوصيل. هل قمت من قبل بالحصول على أيّ طرد عبر شركة DPD؟ أكره عمليات التوصيل، غالبًا ما لا يكون لديك أيّ فكرة عن متى سيصل الطرد الخاصّ بك وهل يجب عليك أن تقوم بمهامك وفقًا لهذا في الصباح أم في المساء. فهذا يعني أنّه يجب عليك أن تكون موجودًا ومستعدًا لرجل تسليم الطرود. لاشيء أبشع من اكتشاف وجود بطاقة تخبرك بالذهاب للمركز البريدي لاحقًا لتسلّم الطرد لأنّك اخترت اللحظة الخاطئة للذهاب إلى الحديقة. تسمح لك DPD بمعرفة موعد وصول الطرد الخاصّ بك بالضبط. تتعامل شركة DPD مع هذا. في يوم التوصيل سيقومون بمراسلتك قبل ساعةٍ من الموعد المتوقّع لوصول الطرد الخاصّ بك. كما وسيقومون أيضًا بالسماح لك بتعقّب طردك في الوقت الحقيقي ومعرفة مكانه عبر الخريطة. هذا يعني أنّه يمكنك معرفة مكان السائق وأن تقرر ما إذا كنت تمتلك 10 دقائق للذهاب إلى المتجر أم لا. بسبب تجربة المستخدم هذا، فإنني أتحمّس بشدّة عندما أكتشف أنني سأستلم طردًا عبر DPD! توفير الإلهاموأخيرًا أريد أن أذكر Etsy. يدرك المدراء في Etsy أنّه هناك الكثير من الأمور التي يمكن الاستفادة منها من قنوات التواصل الإجتماعي غير كونها مجرّد قناة تسويق. يعرفون أنّ العديد من زبائنهم يبحثون عن هديةٍ جيّدة ويحتارون في ماذا يشترون. لهذا قاموا ببناء تطبيق يقوم بالاتصال بحسابات المستخدمين على موقع Facebook. تقوم Etsy بدمج تجربة فيسبوك مع موقعهم عبر عرض اقتراحات الهدايا على المستخدمين عبر تحليل اهتمامات أصدقاء المستخدمين، يقوم التطبيق بتقديم الاقتراحات للمستخدمين عن ماهيّة الهدايا التي يجب عليهم شراؤها. استخدموا التكنولوجيا لتوفير مصدرٍ للإلهام لتجربة الشراء. أليس هذا هو نفسه خدمة العملاء؟ربّما تعتقد أنّ التفكير بهذه الطريقة هو أقرب إلى ما يشبه تصميم خدمة العملاء أكثر منه إلى تصميم تجربة المستخدم. ربّما تكون محقًا. تجربة المستخدمين ليست محدودة بقناةٍ واحدة، جهاز أو قطعة من مشروعك. الحقيقة هي أننا قمنا بتجزئة توصيفات أعمالنا بحيث تتداخل مع بعضها البعض غالبًا. أؤمن أنّ تصميم تجربة المستخدم مهمّ لأكثر من مجال. مجالات تتضمن كلا من تصميم واجهة الاستخدام وخدمة العملاء. مهما كان الاسم الذي تدعوها به، هناك درسٌ مهمّ لتعلّمه، وهو أنّ تجربة المستخدمين ليست محدودة بقناةٍ واحدة، جهاز أو قطعة من مشروعك. ترجمة -وبتصرّف- للمقال User experience design is not what you think لصاحبه Paul Boag. حقوق الصورة البارزة: Designed by Freepik.
  12. إنّ من أكثر ما يجعل العمل مُمتعا هو أن تكون مُتحمسا له، ولكنه قد يصبح أكثر ما يضرنا إذا أصبح هذا الحماس بديلا عن المعرفة وأصبح طريقنا مليئًا بالفرضيات غير المختبرة والرؤية غير الواضحة، ماذا لو كانت أيضا بيئة العمل تؤمن بنظرية العبقري المبدع الذي يعلم كل شيء وتطلب من مصمميها ومطوريها بأن يكونوا هذا العبقري المثالي الذي يستطيع لوحده أن يؤدي وظائفهُ ويتقنها بأكمل وجه، مما يجعلها بيئة عمل غير واقعية تجعل من قول كلمة "لا أعرف" شيئا مستحيلًا. إن النتيجة النهائية لمثل هذه البيئات هو منتجات منفصلة عن الواقع ولا تُلبّي احتياجات ورغبات المستخدمين الحقيقية وبالتالي تصبح منتجات فاشلة لا يُوجد لها أيّ حصة من السوق. إداراك الحاجات والرغبات الحقيقية للناس هو الشّرط الأساسي للتصميم وكما يقول خبراء هذا المجال أن التصميم المتمحور على المستخدم ­User-Centered Design هو جوهر هذا العلم وأساسه. لذلك يجب ألا ننحاز لأنفسنا أو لعملنا أو مهما كانت انحيازاتنا في بناء منتجاتنا ونجعل تركيزنا هو المستخدم النّهائي. هذا وكيف إن كانت المشكلة الرئيسية عند المصممين والمطورين أثناء عملهم هو أنهم يبنون الأشياء لأنفسهم، حيث أنهم متعلقون في الأمور التقنية أكثر من غيرهم من الناس كما قال ذلك الكاتب الأمريكي Michael E. Gerber في كتابه أسطورة الريادي  The E-Myth. هنا تظهر الحاجة الأساسية في فهم حاجات المستخدمين ورغباتهم، وحتى نحقّق ذلك لا بد لنا من عملية بحث مُمَنهجة لنتوصّل إلى نتائج يتم معالجتها، ونبني في ما بعد على أساسها منتجاتنا وبرامجنا. ما هو البحثفي البداية أرجو ألا تفزع من كلمة "بحث" وتنظر إليها على أنها تحتاج إلى وقت ودراسة وأموال حتى يكتمل البحث، فالبحث ببساطة هو تحقيق منظم للوصول إلى المعلومة. أنت تريد أن تعرف أكثر في موضوع معين لذلك أنت تتبع منهجاً معين لزيادة معرفتك في هذا الموضوع، هذا المنهج الذي ستتبعه لزيادة معرفتك يعتمد على من أنت وظيفيًا ومالذي تريد أن تعرفه. واطمئّن، فنوعية البحث المستخدمة هنا ليست بحث أساسي Pure Research معقّدة بمعنى أنه لن تكون مهمتك أن تعرف كيف تتم عملية التذكر في أعصاب الدماغ. في مجالنا هذا يتم الاستعانة بنوعين من الأبحاث للحصول إلى المعلومات هما: البحث الكمّي والبحث النّوعي، كما أن هذان البحثان أيضا هما البحثان اللذان يتم استعمالهما في إنشاء الشركات الناشئة، وهذا لاشتراك هذين المجالين في أنّهما يريدان بناء منتج يريده الناس ويحتاجونه. البحث الكمّي والنّوعيالبحث الكمّي Quantitative Research بحث منهجي للظواهر الاجتماعية من خلال الأساليب الإحصائية، الرياضية أو الحسابية، ويتم الاعتماد على البيانات بشكل رئيسي حتى يتم قياسها رقميا وبالتالي عند بداية فكرة جديدة لا نلجأ إليه لعدم وجود بيانات متعلقة بالمستخدمين وإنما يتم الاستعانة به بعد إصدار النسخة الأولية أو الحد الأدنى من المنتج القاعدي (Minimum Viable Product). البحث النّوعي Qualitative Research بحث يهدف إلى التعمق في فهم سلو الإنسان والأسباب التي دفعته للقيام بهذا السلوك وتكون عينته قليلة وطبيعة أسئلته بكيف، لماذا، وبأي طريقة. توجد طرق كثيرة لجمع البيانات منها المقابلات الفردية والجماعية، الملاحظة ومجموعات التركيز. يعتمد على فهم الناس بتعمق ولعدم وجود أي بيانات تتعلق بالمستخدمين عند بداية المشروع فعادة ما يتم البدء به. تكون نتائجه عبارة عن فرضيات يتم التحقق منها فيما بعد عن طريق البحث الكمي،  كما يتم استخدامه أيضا عند بناء خصائص جديدة للمنتج أو عند وجود غموض في نتائج البحث الكمي. ماذا أريد من البحثالبحث هو أداة بمثابة ميكروسكوب تستطيع من خلالها النظر إلى الصورة كاملة مما تسهل عملية تخطيطنا وإدارتنا. وعند بناء المنتجات يتعين علينا القيام بأربعة بحوث. بحث المنظمة وأصحاب المَصلحةفي هذا البحث تكون الفئة المستهدفة في البحث هم أصحاب المصلحة وفريق العمل المُشترِك معك في بناء المنتج، ويكون هدفه الإجابة عن الأسئلة التالية: مالذي يريد تحقيقه صاحب المنتج وأصحاب العمل، من هم، وما هي أهدافهم؟ماهي التنكولوجيا المستخدمة في بناء المنتج، وما إمكانياتنا وقدراتنا؟بحث المستخدمهو البحث المتعلق بفهم المستخدمين وسلوكهم، ويكون هدفه الإجابه عن هذه الأسئلة: من سيستخدم المنتج، ما مميزاتهم، أعمارهم، جنسهم، هواياتهم، وما سلوكهم الحالي في استخدامهم للمنتج أو لحلول مشابهة؟مالذي يريد تحقيقه المستخدم من استخدامه للمنتج؟وفي حال كان المنتج تطويرًا لنسخة سابقة: ما هي الخصائص والوظائف الحالية أو التعقيدات التي تواجه المستخدم؟ما هي الخصائص والوظائف التي سوف يجدها المستخدم جيدة لو تم إضافتها؟البحث التقييّميإذا قمت ببناء منتجك أو برنامجك بناء على حاجات المستخدم فلا بد لك من التأكد من ذلك والتأكد ما إذا كان منتجك جاهزاً لإصداره للسوق أم لا، هل هو سهل الاستخدام وما هي عيوبه؟ هنا الهدف الرئيسي من البحث التقييمي. بحث المنافسينمن هم منافسيك؟ يجب عليك أن تعرفهم وتقيمهم وتعرف ما هي مُميزّاتهم، خصائصهم وما هو الجديد الذي ستضيفه في منتجك بعد معرفتك لمنافسينك. مع العلم  بأن أصعب منتج منافس لك هو الذي يستخدمه الآن زبائنك المحتملين في المستقبل كما أن الناس كسولون بطبعهم ويكرهون تغيير عاداتهم فلذلك يجب عليك إضافة شيء مميز ورائع في منتجك يحبونه أكثر من كرهم لتغيير هذه العادة. التكلم بتفصيل عن كل هذه البحوث هو خارج نطاقنا هنا لكن سنكتفي بأهمهم وهو بحث المستخدم. بحث المستخدمإذا استطعنا أن نحدد إجابة للسؤال العملي المتعلق بالمنظمة أو الشركة وهو "ما الذي نريده من بناء هذا المنتج" نبدأ بعدها بالطرف الآخر من المعادلة وهو المستخدم وهدفنا كما قلنا أن نعرف ما الذي يريده المستخدمون من هذا المنتج. قد يبدو هذا السؤال بسيطاً وبديهيا لكن أكثر الأسئلة عمقا هي الأسئلة البديهيه. الإثنُوجرَافِي Ethnographyالبعض يسمي البحث النّوعي بالإثنوجرافي وهدفه فهم وتوثيق نشاطات، عقليات وسلوكيات مجموعة من الناس بعد أن يراها الملاحظ أو المراقب ويتم بطرق كثيرة أهمها المقابلة والاستفسار السياقي. المقابلة Interviewلقد ابتدأ العمل، استعد لمقابلة الناس المستهدفة ورتب لذلك وتذكر بأننا نريد أن نفهم السلوك فمثلا إذا كان كان منتجك هو بناء موقع إلكتروني لبيع تذاكر السينما فسنقوم باستهداف رواد السينما وليس محبي الأفلام. الدليلحضر دليل المقابلات بحيث يكون هو المرجع لجميع المقابلات، ومن أهم ما يجب أن يحتويه هذا المرجع هو: مختصر يوضح هدف الدراسة.الديموغرافيا (Demographics) وهي تختلف على اختلاف الدراسة وأهم ما تحتويه الاسم، العمر، الجنس، البلد، الوظيفة والتعليم.مجموعة من الأسئلة لتكسير الحواجز الجليدية في المقابلة، أنت لا تريد فقط أن تنتهي من مقابلتك أنت تريد أن تحصل على أصدق المعلومات لذلك لا بد من أن يخلو الجو من التوتر وتسود الراحة في المكان.الأسئلة الرئيسية في المقابلة وهي الأسئلة المتعلقة بموضوع الدراسة. تجنب أن تسأل أسئلة مثل "ما هو الحل برأيك" وذلك لأن المستخدم ليس مُصمما وغايته ببساطة أن يستخدم المنتج النهائي بسهولة ومتعة.بنية المقابلة (مع مثال توضيحي)المقدمةابدأ بتعريف نفسك مع ابتسامة وعبر عن امتنانك وشكرك له وقدر أنه يعطيك من وقته من أجل هذه المقابلة، خذ معلوماته الشخصية بطريقة مرنة وابدأ بتلطيف الجو في المقابلة واسأله أسئلة في خارج إطار الموضوع، عن تجربته وماذا يحب وما رأيه في موضوع معين بناء على سياق المقابلة وذلك لتكسير الحواجز قبل أن تبدأ في الأسئلة الأساسية في المرحلة التي بعدها. الرئيسيةهنا جوهر المقابلة، ستبدأ بالأسئلة الرئيسية، مثل: أخبرني عن أسبوعك بشكل عام؟ما مدى اهتمامك بالسينما؟هل تذهب للسينما في العطل الأسبوعية، أم لا يوجد وقت محدد؟كيف تتابع آخر الأفلام، وما نوعية الأفلام التي تحبها؟كيف تشتري تذاكرك، هل لديك مشكلة في الدفع عبر الإنترنت؟كم تقضي من الوقت متصلا بالإنترنت، وما هي وسيلة اتصالك به؟كيف تذهب للسينما، مع زوجتك وأولادك أو لوحدك؟ما هي المواقع التي تزورها في العادة؟وكما قلنا ركز على أن تكون الإجابات واقعية وأعر انتباهك لأي إجابة غريبة واستفهم عنها، حاول أن تكون مستمعا جيداً وتجنب التحدث عن نفسك كثيراً. كما أفضل أن تقوم بتدوين الإجابات بعد المقابلة مباشرة وذلك لتجنب تأثير هورثون (وهو أن سلوك الشخص قد يتغير إذا علم أنه مراقب وسُيقدم  السلوك المتوقع تقديمه). الخاتمةاعمل نقلة لطيفة لإنهاء المقابلة واختم مثلا بقولك "انتهيت من الأسئلة، ألديك أي شيء لتضيفه أو أي تعليق بخصوص ما ناقشناه؟". الاستفسار السياقي Contextual Inquiryهي طريقة أخرى من طرق بحث الإثنوجرافي وهو شبيه بالمقابلة إلا أنه يأخذ شكلا أعمق ويختلف عنها بأنه يتطلب الذهاب إلى مكان الحدث مباشرة وأن تراقب المستخدم أثناء قيامه بعمله وتقوم بملاحظته لفهم الموضوع بدقة أكثر ولمعرفة حقيقة الموضوع. ذلك، لأنه في أحسن الظروف في المقابلة النظرية لن تستطيع أن تتذكر كل شيء كمُقابل ولن تدرك حقيقة الشيء إلا إذا قمت بمعاينته وتجربته. لعل أقرب مثال لهذا النوع من البحوث هي قصة إنتاج سيارة Toyota sienna minivan سنة 2004، تم توكيل إنتاج هذه السيارة وتصميمها لـ "يوجي يوكوفا" وكان السوق المستهدف لها هو أمريكا الشمالية التي لم يكون "يوجي" يسكنها أو عنده أي خبرة حقيقة فيها. هنا قام "يوجي" بالذهاب بنفسه إلى أمريكا الشمالية قاطعا بنفس السيارة ولكن بموديل السنة السابقة 2003 ولايات أمريكا الخمسون، أراضي كندا والمكسيك. كان  هدف "يوجي" هو التحدث مع الناس مباشرة  ومراقبتهم أثناء سياقتهم للسيارة ليقوم ببحثه في أرض الحدث مباشرة. بعد أن انتهى من جولته البحثية أنتج الموديل الجديد للسيارة وكانت النتيجة أن مبيعات السيارة لموديل 2004 زادت بنسبة أكثر60% عن السنة السابقة. الجانب العاطفي أثناء البحثالعواطف موضوع خصوصي جداً، كما يبدو أنه من وجهة النظر البسيطة أن هناك فجوة بينه وبين التكنولوجيا. لذا فمن الخطأ أن يتم طرحه بصورة مباشرة في المقابلة، هذا قد يسبب الإحراج للجميع والخطأ في النتائج. هنا يكمن دورك في أن تكون يقظا في أن تفهم الخبايا وأن تقرأ ما بين السطور. راقب أثناء قيامك بالاستفسار السياقي بتأثير الجمال والمتعة على المستخدمين وابحث أيضا في فرص زيادة تأثيرهما. اهتم بالهدف العاطفي من وراء استخدام المنتج، مثلا أنا سأقوم بشراء تذكرتين إلكترونيتين لي ولزوجتي في السينما لقضاء وقت رائع معها، هنا يكون الهدف هو السرور مع الزوجة أكثر من الاستمتاع بالفيلم، ماذا لو كان الهدف هو قضاء وقت الفراغ ليس إلا؟ هنا يكون الدافع هو الملل وليس الشغف في السينما. فهمك لهذه العواطف سيساعدك على تصنيف المستخدمين كما سيجعل من منتجك أكثر عمقاً وبساطة. لمزيد من القراءة كتاب The UX Bookكتاب Just Enough Researchمقال A Five Step Process For Conducting User Researchمقال 5Useful Lies to Tell User Research Participants