تجربه دوربین خود را آشکار کنید

1. قبل از شروع

ویژگی های تاشو چیست؟

تاشوها نوآوری هایی هستند که یک بار در یک نسل تولید می شوند. آن‌ها تجربیات منحصربه‌فردی را ارائه می‌کنند و به همراه آن‌ها فرصت‌های منحصربه‌فردی برای خوشحال کردن کاربران شما با ویژگی‌های متمایز مانند رابط کاربری رومیزی برای استفاده بدون هندز ارائه می‌شود.

پیش نیازها

  • دانش اولیه توسعه برنامه های اندروید
  • دانش اولیه چارچوب تزریق وابستگی هیلت

چیزی که خواهی ساخت

در این کد لبه، شما یک برنامه دوربین با طرح‌بندی‌های بهینه برای دستگاه‌های تاشو می‌سازید.

6caebc2739522a1b.png

شما با یک برنامه دوربین اصلی شروع می‌کنید که به وضعیت هیچ دستگاهی واکنش نشان نمی‌دهد یا از دوربین عقب بهتر برای سلفی‌های پیشرفته استفاده نمی‌کند. شما کد منبع را به‌روزرسانی می‌کنید تا وقتی دستگاه باز می‌شود، پیش‌نمایش را به نمایشگر کوچک‌تر منتقل کنید و به تلفنی که در حالت رومیزی تنظیم می‌شود واکنش نشان دهید.

در حالی که برنامه دوربین راحت‌ترین مورد استفاده برای این API است، هر دو ویژگی که در این کد لبه یاد می‌گیرید می‌توانند برای هر برنامه‌ای اعمال شوند.

چیزی که یاد خواهید گرفت

  • نحوه استفاده از Jetpack Window Manager برای واکنش به تغییر وضعیت بدن
  • چگونه برنامه خود را به صفحه نمایش کوچکتر یک تاشو منتقل کنید

آنچه شما نیاز دارید

  • نسخه اخیر اندروید استودیو
  • یک دستگاه تاشو یا شبیه ساز تاشو

2. راه اندازی شوید

کد شروع را دریافت کنید

  1. اگر Git را نصب کرده اید، می توانید به سادگی دستور زیر را اجرا کنید. برای بررسی اینکه آیا Git نصب شده است، git --version را در ترمینال یا خط فرمان تایپ کنید و بررسی کنید که آیا Git به درستی اجرا می شود.
git clone https://github.com/android/large-screen-codelabs.git
  1. اختیاری: اگر Git ندارید، می‌توانید روی دکمه زیر کلیک کنید تا همه کدهای این لبه کد را دانلود کنید:

ماژول اول را باز کنید

  • در Android Studio، اولین ماژول را در /step1 باز کنید.

اسکرین شات اندروید استودیو که کدهای مربوط به این کد لبه را نشان می دهد

اگر از شما خواسته شد از آخرین نسخه Gradle استفاده کنید، آن را به روز کنید.

3. بدوید و مشاهده کنید

  1. کد را روی ماژول step1 اجرا کنید.

همانطور که می بینید، این یک برنامه دوربین ساده است. می توانید بین دوربین جلو و عقب جابجا شوید و می توانید نسبت تصویر را تنظیم کنید. با این حال، اولین دکمه از سمت چپ در حال حاضر هیچ کاری انجام نمی دهد - اما این نقطه ورود برای حالت سلفی عقب خواهد بود.

a34aca632d75aa09.png

  1. حال سعی کنید دستگاه را در حالت نیمه باز قرار دهید که در آن لولا کاملاً صاف یا بسته نباشد بلکه زاویه 90 درجه را تشکیل دهد.

همانطور که می بینید، برنامه به وضعیت های مختلف دستگاه پاسخ نمی دهد و بنابراین طرح بندی تغییر نمی کند و لولا در وسط منظره یاب باقی می ماند.

4. درباره Jetpack WindowManager بیاموزید

کتابخانه Jetpack WindowManager به توسعه دهندگان برنامه کمک می کند تا تجربیات بهینه سازی شده ای را برای دستگاه های تاشو ایجاد کنند. این شامل کلاس FoldingFeature است که یک چین را در یک صفحه نمایش انعطاف پذیر یا یک لولا بین دو صفحه نمایش فیزیکی را توصیف می کند. API آن دسترسی به اطلاعات مهم مربوط به دستگاه را فراهم می کند:

کلاس FoldingFeature حاوی اطلاعات اضافی مانند occlusionType() یا isSeparating() ، اما این کد کد آن ها را عمیقا بررسی نمی کند.

با شروع از نسخه 1.2.0-beta01 ، این کتابخانه از WindowAreaController استفاده می‌کند، یک API که حالت نمایش عقب را قادر می‌سازد تا پنجره فعلی را به صفحه نمایشی که با دوربین عقب همتراز شده است، منتقل کند، که برای گرفتن سلفی با دوربین عقب و بسیاری از آنها عالی است. موارد استفاده دیگر!

وابستگی ها را اضافه کنید

  • برای استفاده از 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. حالت Rear Selfie را پیاده سازی کنید

با حالت نمایش عقب شروع کنید.

API که اجازه این حالت را می دهد 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. برای مشاهده انتقال محتوای خود به صفحه نمایش بیرونی، " Switch screens now" را انتخاب کنید!

6. حالت Tabletop را پیاده سازی کنید

اکنون زمان آن رسیده است که برنامه خود را تاشو آگاه کنید: محتوای خود را بر اساس جهت تاشو در کنار یا بالای لولای دستگاه حرکت می‌دهید. برای انجام این کار، شما در داخل FoldingStateActor عمل می کنید تا کد شما برای خوانایی راحت تر از Activity جدا شود.

بخش اصلی این API شامل رابط 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 آشنا شدید.

شما آماده اجرای تجربیات کاربری عالی برای برنامه دوربین خود هستید.

در ادامه مطلب

مرجع