1. قبل از شروع
ویژگی های تاشو چیست؟
تاشوها نوآوری هایی هستند که یک بار در یک نسل تولید می شوند. آنها تجربیات منحصربهفردی را ارائه میکنند و به همراه آنها فرصتهای منحصربهفردی برای خوشحال کردن کاربران شما با ویژگیهای متمایز مانند رابط کاربری رومیزی برای استفاده بدون هندز ارائه میشود.
پیش نیازها
- دانش اولیه توسعه برنامه های اندروید
- دانش اولیه چارچوب تزریق وابستگی هیلت
چیزی که خواهی ساخت
در این کد لبه، شما یک برنامه دوربین با طرحبندیهای بهینه برای دستگاههای تاشو میسازید.
شما با یک برنامه دوربین اصلی شروع میکنید که به وضعیت هیچ دستگاهی واکنش نشان نمیدهد یا از دوربین عقب بهتر برای سلفیهای پیشرفته استفاده نمیکند. شما کد منبع را بهروزرسانی میکنید تا وقتی دستگاه باز میشود، پیشنمایش را به نمایشگر کوچکتر منتقل کنید و به تلفنی که در حالت رومیزی تنظیم میشود واکنش نشان دهید.
در حالی که برنامه دوربین راحتترین مورد استفاده برای این API است، هر دو ویژگی که در این کد لبه یاد میگیرید میتوانند برای هر برنامهای اعمال شوند.
چیزی که یاد خواهید گرفت
- نحوه استفاده از Jetpack Window Manager برای واکنش به تغییر وضعیت بدن
- چگونه برنامه خود را به صفحه نمایش کوچکتر یک تاشو منتقل کنید
آنچه شما نیاز دارید
- نسخه اخیر اندروید استودیو
- یک دستگاه تاشو یا شبیه ساز تاشو
2. راه اندازی شوید
کد شروع را دریافت کنید
- اگر Git را نصب کرده اید، می توانید به سادگی دستور زیر را اجرا کنید. برای بررسی اینکه آیا Git نصب شده است،
git --version
را در ترمینال یا خط فرمان تایپ کنید و بررسی کنید که آیا Git به درستی اجرا می شود.
git clone https://github.com/android/large-screen-codelabs.git
ماژول اول را باز کنید
- در Android Studio، اولین ماژول را در
/step1
باز کنید.
اگر از شما خواسته شد از آخرین نسخه Gradle استفاده کنید، آن را به روز کنید.
3. بدوید و مشاهده کنید
- کد را روی ماژول
step1
اجرا کنید.
همانطور که می بینید، این یک برنامه دوربین ساده است. می توانید بین دوربین جلو و عقب جابجا شوید و می توانید نسبت تصویر را تنظیم کنید. با این حال، اولین دکمه از سمت چپ در حال حاضر هیچ کاری انجام نمی دهد - اما این نقطه ورود برای حالت سلفی عقب خواهد بود.
- حال سعی کنید دستگاه را در حالت نیمه باز قرار دهید که در آن لولا کاملاً صاف یا بسته نباشد بلکه زاویه 90 درجه را تشکیل دهد.
همانطور که می بینید، برنامه به وضعیت های مختلف دستگاه پاسخ نمی دهد و بنابراین طرح بندی تغییر نمی کند و لولا در وسط منظره یاب باقی می ماند.
4. درباره Jetpack WindowManager بیاموزید
کتابخانه Jetpack WindowManager به توسعه دهندگان برنامه کمک می کند تا تجربیات بهینه سازی شده ای را برای دستگاه های تاشو ایجاد کنند. این شامل کلاس FoldingFeature
است که یک چین را در یک صفحه نمایش انعطاف پذیر یا یک لولا بین دو صفحه نمایش فیزیکی را توصیف می کند. API آن دسترسی به اطلاعات مهم مربوط به دستگاه را فراهم می کند:
-
state()
FLAT
برمیگرداند اگر لولا در 180 درجه باز شود یاHALF_OPENED
در غیر این صورت. -
orientation()
FoldingFeature.Orientation.HORIZONTAL
را برمی گرداند اگر عرضFoldingFeature
بزرگتر از ارتفاع باشد. در غیر این صورت،FoldingFeature.Orientation.VERTICAL
را برمی گرداند. -
bounds()
مرزهایFoldingFeature
را در قالبRect
ارائه می کند.
کلاس 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.
- این متغیرها را در
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
- و آنها را در متد
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()
}
}
}
- اکنون تابع
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.
- اکنون تابع
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
را پیاده سازی کنید.
- اعلان
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]")
}
- اپلیکیشن را بسازید و اجرا کنید. اگر دستگاه خود را باز کنید و روی دکمه نمایشگر عقب ضربه بزنید، پیامی مانند این از شما خواسته می شود:
- برای مشاهده انتقال محتوای خود به صفحه نمایش بیرونی، " Switch screens now" را انتخاب کنید!
6. حالت Tabletop را پیاده سازی کنید
اکنون زمان آن رسیده است که برنامه خود را تاشو آگاه کنید: محتوای خود را بر اساس جهت تاشو در کنار یا بالای لولای دستگاه حرکت میدهید. برای انجام این کار، شما در داخل FoldingStateActor
عمل می کنید تا کد شما برای خوانایی راحت تر از Activity
جدا شود.
بخش اصلی این API شامل رابط WindowInfoTracker
است که با یک متد استاتیک ایجاد شده است که به یک Activity
نیاز دارد:
step1/CameraCodelabDependencies.kt
@Provides
fun provideWindowInfoTracker(activity: Activity) =
WindowInfoTracker.getOrCreate(activity)
نیازی نیست این کد را همانطور که قبلاً وجود دارد بنویسید، اما برای درک نحوه ساخت WindowInfoTracker
مفید است.
- برای گوش دادن به هرگونه تغییر پنجره، به این تغییرات در متد
onResume()
Activity
خود گوش دهید:
step1/MainActivity.kt
lifecycleScope.launch {
foldingStateActor.checkFoldingState(
this@MainActivity,
binding.viewFinder
)
}
- اکنون، فایل
FoldingStateActor
را باز کنید، زیرا زمان پر کردن متدcheckFoldingState()
فرا رسیده است.
همانطور که قبلاً دیدید، در مرحله RESUMED
Activity
شما اجرا میشود و از WindowInfoTracker
برای گوش دادن به هر تغییر طرحبندی استفاده میکند.
step1/FoldingStateActor.kt
windowInfoTracker.windowLayoutInfo(activity)
.collect { newLayoutInfo ->
activeWindowLayoutInfo = newLayoutInfo
updateLayoutByFoldingState(cameraViewfinder)
}
با استفاده از رابط WindowInfoTracker
، می توانید windowLayoutInfo()
را فراخوانی کنید تا Flow
از WindowLayoutInfo
را که حاوی تمام اطلاعات موجود در DisplayFeature
است جمع آوری کنید.
آخرین مرحله این است که به این تغییرات واکنش نشان دهید و محتوا را بر اساس آن جابجا کنید. این کار را در متد updateLayoutByFoldingState()
، یک مرحله در یک زمان انجام می دهید.
- مطمئن شوید که
activityLayoutInfo
دارای برخی از ویژگی هایDisplayFeature
است و حداقل یکی از آنهاFoldingFeature
باشد، در غیر این صورت نمی خواهید کاری انجام دهید:
step1/FoldingStateActor.kt
val foldingFeature = activeWindowLayoutInfo?.displayFeatures
?.firstOrNull { it is FoldingFeature } as FoldingFeature?
?: return
- موقعیت تاشو را محاسبه کنید تا مطمئن شوید موقعیت دستگاه بر چیدمان شما تأثیر می گذارد و خارج از محدوده سلسله مراتب شما نیست:
step1/FoldingStateActor.kt
val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
foldingFeature,
cameraViewfinder.parent as View
) ?: return
اکنون، مطمئن هستید که یک FoldingFeature
دارید که بر طرحبندی شما تأثیر میگذارد، بنابراین باید محتوای خود را جابهجا کنید.
- بررسی کنید که آیا
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
باشد، محتوای خود را به سمت راست منتقل میکنید، در غیر این صورت آن را در بالای موقعیت تاشو قرار میدهید.
- برنامه خود را بسازید و اجرا کنید، و سپس دستگاه خود را باز کنید و آن را در حالت رومیزی قرار دهید تا مشاهده کنید که محتوا مطابق آن حرکت می کند!
7. تبریک می گویم!
در این کد لبه با برخی از قابلیتهای منحصر به فرد دستگاههای تاشو، مانند حالت نمایش عقب یا حالت تبلتاپ و نحوه باز کردن قفل آنها با استفاده از Jetpack WindowManager آشنا شدید.
شما آماده اجرای تجربیات کاربری عالی برای برنامه دوربین خود هستید.