استكشاف تجربة الكاميرا

1. قبل البدء

ما الذي يميّز الأجهزة القابلة للطي؟

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

المتطلبات الأساسية

  • معرفة أساسية بتطوير تطبيقات Android
  • معرفة أساسية بإطار عمل حقن التبعية لأداة Hilt

ما الذي ستنشئه

في هذا الدرس التطبيقي حول الترميز، أنشئ تطبيق كاميرا بتنسيقات محسَّنة للأجهزة القابلة للطي.

6caebc2739522a1b.png

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

على الرغم من أنّ تطبيق الكاميرا هو حالة الاستخدام الأكثر ملاءمةً لواجهة برمجة التطبيقات هذه، يمكن تطبيق الميزتَين اللتَين تتعلّمهما في هذا الدرس التطبيقي حول الترميز على أي تطبيق.

المعلومات التي ستطّلع عليها

  • طريقة استخدام Jetpack Window Manager للتفاعل مع وضعية تغيير الوضع
  • كيفية نقل تطبيقك إلى شاشة أصغر من جهاز قابل للطي

المتطلبات

  • إصدار حديث من "استوديو Android"
  • جهاز قابل للطي أو محاكي قابل للطي

2. الإعداد

الحصول على رمز البدء

  1. إذا كان Git مثبتًا لديك، فيمكنك ببساطة تشغيل الأمر أدناه. للتحقق مما إذا تم تثبيت Git، اكتب git --version في الوحدة الطرفية أو سطر الأوامر وتأكد من عمله بشكل صحيح.
git clone https://github.com/android/large-screen-codelabs.git
  1. اختياري: إذا لم يكن لديك حساب Git، يمكنك النقر على الزر التالي لتنزيل جميع الرموز الخاصة بهذا الدرس التطبيقي:

فتح الوحدة الأولى

  • في "استوديو Android"، افتح الوحدة الأولى ضمن /step1.

لقطة شاشة من "استوديو Android" تعرض الرمز البرمجي المتعلّق بهذا الدرس التطبيقي حول الترميز

إِذَا كَانَ الْمَطْلُوبُ اسْتِخْدَامْ أَحْدَثْ إِصْدَارْ مِنْ Gradle، يُرْجَى تَحْدِيثُهُ.

3- الركض والمراقبة

  1. شغِّل الرمز في الوحدة step1.

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

a34aca632d75aa09.png

  1. والآن، حاول وضع الجهاز في وضع نصف مفتوح، حيث لا تكون المفصّلة مسطحة أو مغلقة تمامًا، ولكنها تشكل زاوية تبلغ 90 درجة.

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

4. مزيد من المعلومات حول Jetpack WindowManager

تساعد مكتبة Jetpack WindowManager مطوّري التطبيقات في إنشاء تجارب محسَّنة للأجهزة القابلة للطي. يتضمّن هذا المستند الفئة FoldingFeature التي تصف الطي في شاشة عرض مرنة أو مع مفصلة بين لوحتَي عرض. وتوفِّر واجهة برمجة التطبيقات إمكانية الوصول إلى المعلومات المُهمّة المتعلّقة بالجهاز:

تحتوي الفئة FoldingFeature على معلومات إضافية، مثل occlusionType() أو isSeparating()، إلا أنّ هذا الدرس التطبيقي حول الترميز لا يستكشف هذه المعلومات بالتفصيل.

بدءًا من الإصدار 1.2.0-beta01، تستخدم المكتبة WindowAreaController، وهي واجهة برمجة تطبيقات تتيح لميزة "وضع العرض الخلفي" نقل النافذة الحالية إلى الشاشة المتوافقة مع الكاميرا الخلفية، وهي طريقة رائعة لالتقاط الصور الذاتية باستخدام الكاميرا الخلفية والعديد من حالات الاستخدام الأخرى.

إضافة التبعيات

  • لاستخدام Jetpack WindowManager في تطبيقك، يجب إضافة العناصر التابعة التالية إلى ملف build.gradle على مستوى الوحدة:

step1/build.gradle

def work_version = '1.2.0-beta01'
implementation "androidx.window:window:$work_version"
implementation "androidx.window:window-java:$work_version"
implementation "androidx.window:window-core:$work_version"

يمكنك الآن الوصول إلى كلّ من الصفَّين FoldingFeature وWindowAreaController في تطبيقك. يمكنك استخدامها لتقديم أفضل تجربة كاميرا قابلة للطي.

5- تفعيل وضع "الصور الذاتية الخلفية"

تفعيل وضع "الشاشة الخلفية"

واجهة برمجة التطبيقات التي تسمح بهذا الوضع هي واجهة برمجة التطبيقات WindowAreaController التي توفّر المعلومات والسلوكيات المتعلّقة بنقل النوافذ بين الشاشات أو مناطق العرض على الجهاز.

تتيح لك هذه الميزة طلب البحث عن قائمة "WindowAreaInfo" المتاحة حاليًا للتفاعل معها.

باستخدام WindowAreaInfo، يمكنك الوصول إلى WindowAreaSession، وهي واجهة لعرض ميزة مساحة نافذة نشطة وحالة مدى توفُّر جهاز WindowAreaCapability. محدّد.

  1. يُرجى تعريف هذه المتغيّرات في MainActivity:

step1/MainActivity.kt

private lateinit var windowAreaController: WindowAreaController
private lateinit var displayExecutor: Executor
private var rearDisplaySession: WindowAreaSession? = null
private var rearDisplayWindowAreaInfo: WindowAreaInfo? = null
private var rearDisplayStatus: WindowAreaCapability.Status =
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED
private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
  1. ويمكنك إعدادها باستخدام طريقة onCreate():

step1/MainActivity.kt

displayExecutor = ContextCompat.getMainExecutor(this)
windowAreaController = WindowAreaController.getOrCreate()

lifecycleScope.launch(Dispatchers.Main) {
  lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
    windowAreaController.windowAreaInfos
      .map{info->info.firstOrNull{it.type==WindowAreaInfo.Type.TYPE_REAR_FACING}}
      .onEach { info -> rearDisplayWindowAreaInfo = info }
      .map{it?.getCapability(rearDisplayOperation)?.status?:  WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED }
      .distinctUntilChanged()
      .collect {
           rearDisplayStatus = it
           updateUI()
      }
  }
}
  1. والآن، عليك تنفيذ وظيفة "updateUI()" لتفعيل زر الصورة الذاتية في الخلفية أو إيقافه، بناءً على الحالة الحالية:

step1/MainActivity.kt

private fun updateUI() {
    if(rearDisplaySession != null) {
        binding.rearDisplay.isEnabled = true
        // A session is already active, clicking on the button will disable it
    } else {
        when(rearDisplayStatus) {
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> {
                binding.rearDisplay.isEnabled = false
                // RearDisplay Mode is not supported on this device"
            }
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> {
                binding.rearDisplay.isEnabled = false
                // RearDisplay Mode is not currently available
            }
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> {
                binding.rearDisplay.isEnabled = true
                // You can enable RearDisplay Mode
            }
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> {
                binding.rearDisplay.isEnabled = true
                // You can disable RearDisplay Mode
            }
            else -> {
                binding.rearDisplay.isEnabled = false
                // RearDisplay status is unknown
            }
        }
    }
}

هذه الخطوة الأخيرة اختيارية، ولكن من المفيد جدًا معرفة كل حالات WindowAreaCapability. الممكنة.

  1. نفِّذ الآن الدالة toggleRearDisplayMode، التي ستغلق الجلسة إذا كانت الإمكانية نشطة بالفعل، أو تستدعي الدالة transferActivityToWindowArea:

step1/cameraViewModel.kt

private fun toggleRearDisplayMode() {
    if(rearDisplayStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) {
        if(rearDisplaySession == null) {
            rearDisplaySession = rearDisplayWindowAreaInfo?.getActiveSession(rearDisplayOperation)
        }
        rearDisplaySession?.close()
    } else {
        rearDisplayWindowAreaInfo?.token?.let { token ->
            windowAreaController.transferActivityToWindowArea(
                token = token,
                activity = this,
                executor = displayExecutor,
                windowAreaSessionCallback = this
            )
        }
    }
}

لاحِظ استخدام MainActivity باعتباره WindowAreaSessionCallback.

تتوافق واجهة برمجة التطبيقات Rear Display API مع طريقة المستمع: عندما تطلب نقل المحتوى إلى الشاشة الأخرى، تبدأ جلسة يتم عرضها باستخدام طريقة onSessionStarted() التي يستخدمها المستمع. إذا أردت بدلاً من ذلك العودة إلى الشاشة الداخلية (والأكبر حجمًا)، يتم إغلاق الجلسة، وستحصل على تأكيد بطريقة onSessionEnded(). لإنشاء أداة معالجة أحداث مماثلة، عليك تنفيذ واجهة WindowAreaSessionCallback.

  1. تعديل تعريف MainActivity لتنفيذ الواجهة WindowAreaSessionCallback:

step1/MainActivity.kt

class MainActivity : AppCompatActivity(), WindowAreaSessionCallback

الآن، نفِّذ الطريقتين onSessionStarted وonSessionEnded في MainActivity. وتكون طرق معاودة الاتصال هذه مفيدة للغاية لتلقّي إشعار بحالة الجلسة وتحديث التطبيق وفقًا لذلك.

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

step1/MainActivity.kt

override fun onSessionEnded(t: Throwable?) {
    if(t != null) {
        Log.d("Something was broken: ${t.message}")
    }else{
        Log.d("rear session ended")
    }
}

override fun onSessionStarted(session: WindowAreaSession) {
    Log.d("rear session started [session=$session]")
}
  1. أنشئ التطبيق وشغِّله. إذا فتحت جهازك ثم نقرت على زر الشاشة الخلفية، ستظهر لك رسالة مثل هذه:

3fa50cce0b0d4b8d.png

  1. اختَر التبديل بين الشاشات الآن للاطّلاع على المحتوى الذي تم نقله إلى الشاشة الخارجية.

6- تفعيل وضع "التثبيت على سطح مستوٍ"

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

يكمن الجزء الأساسي من واجهة برمجة التطبيقات هذه في واجهة WindowInfoTracker، التي يتم إنشاؤها بطريقة ثابتة تتطلّب Activity:

step1/cameraCodelabDependencies.kt

@Provides
fun provideWindowInfoTracker(activity: Activity) =
        WindowInfoTracker.getOrCreate(activity)

لا تحتاج إلى كتابة هذا الرمز لأنّه متوفّر حاليًا، ولكن من المفيد فهم طريقة إنشاء WindowInfoTracker.

  1. للاستماع إلى أي تغيير في النافذة، استمِع إلى هذه التغييرات في طريقة onResume() في Activity:

step1/MainActivity.kt

lifecycleScope.launch {
    foldingStateActor.checkFoldingState(
         this@MainActivity, 
         binding.viewFinder
    )
}
  1. والآن، افتح ملف FoldingStateActor، حيث حان وقت ملء طريقة checkFoldingState().

كما سبق ورأيت، يتم تشغيله في مرحلة RESUMED من Activity ويستفيد من WindowInfoTracker للاستماع إلى أي تغيير في التنسيق.

step1/FoldingStateActor.kt

windowInfoTracker.windowLayoutInfo(activity)
      .collect { newLayoutInfo ->
         activeWindowLayoutInfo = newLayoutInfo
         updateLayoutByFoldingState(cameraViewfinder)
      }

باستخدام واجهة WindowInfoTracker، يمكنك استدعاء windowLayoutInfo() لجمع Flow من WindowLayoutInfo يحتوي على جميع المعلومات المتاحة في DisplayFeature.

الخطوة الأخيرة هي التفاعل مع هذه التغييرات ونقل المحتوى وفقًا لذلك. يتم إجراء ذلك داخل طريقة updateLayoutByFoldingState()، بمعدل خطوة واحدة في كل مرة.

  1. تأكَّد من أنّ activityLayoutInfo يحتوي على بعض سمات DisplayFeature، وأنّ واحدًا منها على الأقل يحمل التصنيف FoldingFeature، وإلّا فلن تريد اتخاذ أي إجراء:

step1/FoldingStateActor.kt

val foldingFeature = activeWindowLayoutInfo?.displayFeatures
            ?.firstOrNull { it is FoldingFeature } as FoldingFeature?
            ?: return
  1. احسب موضع الجزء المرئي من الصفحة للتأكد من أنّ موضع الجهاز يؤثر في التنسيق وليس خارج حدود التسلسل الهرمي:

step1/FoldingStateActor.kt

val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
            foldingFeature,
            cameraViewfinder.parent as View
        ) ?: return

الآن، أنت متأكد من أن لديك FoldingFeature يؤثر في التنسيق، لذلك يجب نقل المحتوى.

  1. تحقَّق مما إذا كانت قيمة FoldingFeature هي HALF_OPEN، وإلا ستستعيد موضع المحتوى. إذا كانت القيمة هي HALF_OPEN، يجب إجراء عملية تحقّق أخرى والعمل بشكل مختلف استنادًا إلى اتجاه الجزء المرئي من الصفحة:

step1/FoldingStateActor.kt

if (foldingFeature.state == FoldingFeature.State.HALF_OPENED) {
    when (foldingFeature.orientation) {
        FoldingFeature.Orientation.VERTICAL -> {
            cameraViewfinder.moveToRightOf(foldPosition)
        }
        FoldingFeature.Orientation.HORIZONTAL -> {
            cameraViewfinder.moveToTopOf(foldPosition)
        }
    }
} else {
    cameraViewfinder.restore()
}

إذا كان الجزء المرئي من الصفحة VERTICAL، سيتم نقل المحتوى إلى اليسار أو نقله في أعلى موضع الجزء المرئي من الصفحة.

  1. صمِّم تطبيقك وشغِّله، ثم افتح جهازك وضَعه في وضع "التثبيت على سطح مستوٍ" لرؤية المحتوى يتحرك وفقًا لذلك.

7. تهانينا!

في هذا الدرس التطبيقي حول الترميز، تعرّفت على بعض الإمكانات التي ينفرد بها الأجهزة القابلة للطي، مثل وضع العرض الخلفي أو وضع "التثبيت على سطح مستوٍ"، وكيفية فتح قفلهما باستخدام Jetpack WindowManager.

أنت على استعداد لتنفيذ تجارب مستخدم رائعة لتطبيق الكاميرا.

قراءة إضافية

Reference