با فراتر رفتن ضرایب شکل جدید از صفحهنمایش لمسی، سیستم ورودی Compose برای پاسخگویی به این چالش تکامل یافته است. معماری لایهای آن پایهای برای پشتیبانی یکپارچه از همه چیز، از اشارههای پیشرفته پد لمسی گرفته تا ورودیهای سختافزاری جدید، فراهم میکند.
ازطریق تفکیکهای معماری واضح و نمونههای کد عملی، چرخه حیات یک رویداد ورودی را از لایه پلاتفرم سیستمعامل تا آشکارسازهای اشاره Compose و مؤلفههای واسط کاربر دنبال کنید. با نحوه ردیابی رویدادها در کل پشته، اعمال الگوهای معماری کلیدی برای انواع ورودیهای نوظهور، و اشکالزدایی مطمئن تعاملهای پیچیده اشارهای در برنامههای خودتان آشنا خواهید شد.
performTrackpadInput فراتر از لمس گسترش یافته است. این میاناهای برنامهسازی کاربردی سطوح آزمایشی آشنایی را برای اعتبارسنجی اینکه برنامهها دستگاههای غیرلمسی را بهدرستی مدیریت میکنند ارائه میدهند.آوانویسی
سلام به همه!
امیدواریم در «نشست توسعهدهندگان Android» اوقات خوبی داشته باشید و از اینکه در گفتگوی ما شرکت کردید سپاسگزاریم.
من الکس وانیو، مهندس نرمافزار در تیم Compose هستم و جرمی، که او هم مهندس نرمافزار در تیم Compose است، به من پیوسته است.
هر دوی ما در تلاش بودهایم تا سیستم ورودی Jetpack Compose را بهبود بخشیم و امروز قصد داریم درباره نحوه مقیاسبندی آنها فراتر از صفحه لمسی صحبت کنیم تا پشتیبانی از ورودی غیرلمسی بهصورت پیشفرض آسانتر شود.
ورودی، بهطور کلی، هر روشی را که کاربر با برنامه شما تعامل برقرار میکند ضبط میکند.
صفحههای لمسی احتمالاً اولین چیزی هستند که برای برنامه تلفن همراه به ذهنتان میرسد، اما قطعاً تنها نوع ورودی نیستند.
انواع دیگر ورودی شامل صفحهکلید فیزیکی، موشواره، پد لمسی، قلم، کنترلکننده، و دستگاههای چرخشی میشود.
همچنین شاهد انواع ورودیهای جدید و نوآورانه هستیم، مانند پد لمسی در افزاردید هوشمند، ردگیری چشم و دست در واقعیت گسترده، و اشارههای انگشت و مچ در Wear.
و اگر بخواهید حتی کلیتر نگاه کنید، انواع دیگری از ورودی نیز وجود دارد: چیزهایی مانند IMEهای نرمافزاری برای ویرایش نوشتار، میکروفونها، دوربینها، و دیگر حسگرهای دستگاه.
باید انتظار داشته باشید که از برنامههایتان با انواع مختلفی از ورودیها استفاده شود.
یکیاز دلایل این امر تنوع روزافزون دستگاههایی است که برنامهها روی آنها اجرا میشوند و سازگاری فزاینده این دستگاهها است.
ضریبهای شکل دستگاه ممکن است رایجترین روش ورودی را داشته باشند، اما مهم است که فرض نکنید ضریب شکل دستگاه خاصی بهمعنای استفاده از نوع ورودی خاصی است.
اگرچه Chromebook دارای پد لمسی و صفحهکلید داخلی است، اما این امر باعث نمیشود که این نوع ورودیها منحصر به آن فرم فاکتور باشند.
باید انتظار داشته باشید که صفحهکلیدهای فیزیکی، پدهای لمسی، و موشوارهها در تلفنها، رایانههای لوحی، و تلویزیونها نیز استفاده شوند و حتی برخیاز تلویزیونها صفحهنمایش لمسی دارند.
دلیل مهم دوم برای انتظار و پشتیبانی از انواع ورودیهای متعدد، دسترسپذیری است.
اجازه دادن به کاربران برای استفاده از دستگاه ورودی انتخابیشان و اجازه دادن به آنها برای تغییر دستگاههای ورودی به هر صورتی که مناسب میدانند، کار درستی برای دردسترستر و قابلاستفادهتر کردن برنامهها است.
و بسته به حوزه قضایی، ممکن است داشتن رفتار خاص و سازگار در برنامه شما برای این نوع ورودیها، بهویژه درمورد مواردی مانند صفحهکلید و موشواره، یک الزام قانونی باشد.
بهطور خلاصه، باید بدون توجه به ضریب شکل، پشتیبانی از انواع مختلف ورودی را ایجاد کنید.
بنابراین، بیایید ببینیم Jetpack Compose چگونه به برنامههای شما کمک میکند تا این نیازهای کاربر را برآورده کنند.
فلسفه Compose، بهطور کلی، این است که این انواع مختلف ورودی باید بهطور مناسب با مؤلفههای استاندارد بدون نیاز به انجام کار خاصی رفتار کنند.
و سپس، باید ابزارهایی را که برای سفارشیسازی و تغییر رفتار نیاز دارید، بهگونهای که مناسب میدانید، داشته باشید.
بنابراین، علاوهبر آن انواع ورودی اولیه، با پشتیبانی شدن انواع ورودی بیشتر توسط پلاتفرم زیربنایی، قصد داریم پشتیبانی از آنها را در مؤلفههای Compose ایجاد کنیم و رفتار برنامههایتان را فقط با بهروزرسانی نسخه Compose شما بهبود بخشیم.
در این گفتگو، دو نمونه از این موارد را که اخیراً به Compose اضافه شدهاند بررسی خواهیم کرد.
بهطور اتفاقی، هر دو دستگاه شبیه پد لمسی هستند، اما همانطور که خواهید دید، به روشهای بسیار متفاوتی استفاده میشوند.
بنابراین، بیایید با برخی کارهای اخیر روی پدهای لمسی از نوع سبک رایانه کیفی شروع کنیم.
پد لمسی دستگاه ورودی مبتنی بر اشارهگر است.
بهطور دقیقتر، وقتی از پد لمسی استفاده میکنید، مختصات دوبعدی دریافت میکنید.
اما برخلاف صفحه لمسی، مختصات انگشتان روی پد لمسی مستقیماً در فضای مختصات واسط کاربر شما قرار ندارند.
درعوض، برای پد لمسی شبیه رایانه کیفی، ترجمهای برای کنترل مکاننما وجود دارد.
در این ترجمه، میتوانید انتظار یک دسته تبدیل پلاتفرم پیچیده را داشته باشید: چیزهایی مثل شتاب مکاننما و رد کردن کف دست.
بنابراین، آنچه در سمت Compose دریافت میکنید، یک اشارهگر مربوط به مکان مکاننما است که معمولاً مانند ورودی موشواره عمل میکند.
از پلاتفرم Android، اطلاعات نگه داشتن نشانگر، کلیکها ازجمله کلیکهای ثانویه یا ثالث را دریافت میکنیم و همه این اطلاعات در MotionEvents قرار میگیرد.
برای درک اینکه چگونه رفتار را در جدیدترین نسخههای Compose بهبود دادهایم، ابتدا بیایید نگاهی دقیقتر به نمای کلی نحوه مدیریت این رویدادهای اشارهگر توسط Compose داشته باشیم.
من این را به شش مرحله مختلف تقسیم میکنم و ما به نوبت همه اینها را بررسی خواهیم کرد.
فاز اول MotionEvents است که به Compose منتقل میشود.
بنابراین، Compose درواقع، در پشت صحنه، فقط یک نما است: یک نمای واقعاً پیچیده که کارهای زیادی انجام میدهد، اما فقط یک نما است، به این معنی که باید در کل سیستم Android برای ورودی اشارهگر کار کند.
بنابراین، در Compose، این بدان معناست که Compose، همانطور که در داخل یک پنجره معمولی در داخل یک فعالیت معمولی زندگی میکند، MotionEvents را دریافت میکند: MotionEvents پلتفرم Android.
سپس Compose باید آنها را مدیریت کند و هر کاری را که میخواهید در Compose پیرامون ورودی اشارهگر انجام دهید، انجام دهد.
اولین مرحله در اینجا ترجمه آن MotionEvents به یک نمایش داخلی مستقل از پلاتفرم در Compose است که آن را PointerInputEvent مینامیم.
این یک نوع داخلی است، بنابراین آن را مستقیماً نخواهید دید.
و اگر میخواهید همه جزئیات آنچه را که در اینجا اتفاق میافتد ببینید، میتوانید به MotionEventAdapter نگاه کنید، اما ما در اینجا از این جزئیات صرفنظر میکنیم.
بنابراین، اکنون یک نمایش مستقل از پلاتفرم از MotionEvent در Compose داریم.
سپس، مشخص میشود که ورودی معمولاً درطول زمان اتفاق میافتد.
شما یک سری رویدادهای ورودی را یکی پس از دیگری دریافت میکنید، بنابراین واقعاً مفید است که بدانید در طول زمان چه اتفاقی میافتد.
مواردی مانند اشارهها، ردیابی سرعت، تشخیص لغزش، همه اینها باید درک درستی از آنچه در هر نقطه از زمان اتفاق میافتد داشته باشند.
بنابراین، مرحله بعدی در این خط لوله پردازش، افزودن این اطلاعات زمانی است و براساس رویدادهای اشارهگر متوالی، پیشپردازشهایی انجام خواهد شد.
در کد، میتوانید این را بهعنوان گرفتن PointerInputEvent فعلی، ترکیب آن با آنچه در مورد قبلی اتفاق افتاده است، در نظر بگیرید و سپس یک InternalPointerEvent آگاه از زمان ایجاد میکنیم.
این نوع داخلی دیگری است، همانطور که از نامش پیداست، اما میتوانید آن را بهعنوان نقشهای از شناسههای اشارهگر مختلف برای تغییرات ورودی اشارهگر درنظر بگیرید، و PointerInputChanges یک نوع عمومی است.
این اولین مورد ما است.
و چیزی که ممکن است با فکر کردن درباره شناسههای اشارهگر مختلف متوجه شوید این است که اکنون که از ردیابی زمان آگاه هستیم، میتوانیم زمان آمدن و رفتن شناسههای اشارهگر را نیز پیگیری کنیم.
فقط یک مورد نیست.
اگر چند انگشت روی صفحه لمسی داشته باشید، ممکن است چند اشارهگر بهطور همزمان ظاهر و ناپدید شوند و ما باید این را مدیریت کنیم.
بازهم اگر میخواهید به جزئیات بپردازید، میتوانید به PointerInputChangeEventProducer نگاه کنید که همه این کارها را انجام میدهد، اما ما از آن میگذریم.
هنوز کارهای زیادی برای انجام دادن داریم.
بنابراین، اکنون یک نمایش Compose از ورودی داریم و اطلاعات زمانی دارد.
مرحله بعدی درواقع این است که بفهمیم با آن رویداد چه کنیم.
آن را به کجا ارسال کنیم؟
و این به آزمایش «مسیر ضربه» منتهی میشود.
بنابراین، بیایید وضعیتی را تصور کنیم که در آن یک MotionEvent ارسال میشود و این ComposeView کل پنجره، شاید کل صفحه را پر میکند.
باید ببینیم کجا باید آن را ارسال کنیم.
و چون Compose فقط یک نما است، اما ما چیزهای زیادی در داخل Compose داریم، یک دسته از گرههای مختلف، دکمهها، چیزهایی که با آنها تعامل دارید، باید در Compose تمام کارهای مربوط به تعیین اینکه رویدادی که در یک موقعیت خاص اتفاق میافتد به کجا ارسال شود را انجام دهیم و بفهمیم کدام گره واقعاً به آن علاقهمند است.
این گرههای چیدمان دارای مختصات، مرز، و شکل هستند، بنابراین باید مشخص کنیم که کدامیک، درصورت وجود، مورد اصابت قرار گرفته است.
اما این هم کلیترین دیدگاه نیست.
معلوم شده است که ما دوست داریم چیزهای تعاملی را داخل چیزهای تعاملی دیگر قرار دهیم، مثلاً قرار دادن دکمه داخل فهرست.
معلوم میشود که ما این کار را زیاد انجام میدهیم.
بنابراین، حالت کلی کامل در اینجا این است که یک رویداد ورودی خاص یک «مسیر ضربه» کامل ایجاد میکند که از هر گره قابل تعاملی که توسط مختصات رویداد ورودی ضربه میخورد عبور میکند.
بنابراین، در این مورد، سه مورد وجود دارد که در سلسله مراتب تودرتو قرار میگیرد.
بنابراین، ما یک Hit Path داریم، اما این حتی کافی نیست.
یادتان هست گفتم چندین رویداد ورودی اشارهگر داریم، چندین اشارهگر که ممکن است پایین باشند؟
درواقع ما باید یک «درخت مسیر کلیک» کامل داشته باشیم.
وقتی چند اشارهگر دارید، فقط یک «مسیر ضربه» وجود ندارد.
بنابراین، مرحله بعدی ایجاد و بهروزرسانی این «درخت مسیر ضربه» است.
بنابراین، این نگاه دقیقتری به آنچه درحال رخ دادن است میباشد.
در سمت چپ، گرههایی داریم که بهصورت بصری چیده شدهاند، و سپس در پایین سمت چپ، میتوانید نمایش سلسلهمراتبی را ببینید، کدام گرهها داخل گرههای دیگر هستند، کدامها اجداد یا فرزندان یکدیگر هستند.
وقتی اشارهگر پایین میرود، آن «مسیر ضربه» را که مسیر کامل در درخت است ردیابی میکنیم و سپس «مسیر ضربه» را برای آن اشارهگر خاص دریافت میکنیم.
وقتی اشارهگر دوم را دریافت میکنیم، این «مسیر ضربه» به درخت تبدیل میشود.
ممکن است با برخیاز گرههای مشابه با گره دیگر تلاقی داشته باشد، اما ممکن است با گرههای دیگر نیز تعامل داشته باشد.
بنابراین، در سمت راست، «درخت مسیر ضربه» را میبینید که اکنون آن را حفظ میکنیم.
و اشارهگرها میتوانند بیایند و بروند، بنابراین ما باید خودمان این را درطول زمان حفظ کنیم تا مطمئن شویم که فهرست کامل گرههایی را که در این لحظه به هر رویداد درحال انجام علاقهمند هستند دریافت میکنیم.
بسیار خوب.
بنابراین، اکنون یک «درخت مسیر ضربه» داریم که برای این رویدادهای ورودی نگهداری میکنیم، یعنی به مرحله بعدی رسیدهایم.
اکنون، ما در واقع میدانیم که کجا قرار است این رویدادهای ورودی اشارهگر را ارسال کنیم، کدام گرهها به هر رویداد خاص علاقهمند هستند.
اکنون که این را میدانیم، میتوانیم تبدیل مختصات را انجام دهیم.
Turns out it's really nice if your node gets input events within its own local coordinate space, so you don't have to do a whole bunch of, you know, coordinate math yourself.
بنابراین، وقتی این را بدانیم، میتوانیم رویداد ورودی اشارهگر را بگیریم، آن را با مختصات گره موردنظر ترکیب کنیم، و سپس این درنهایت آن PointerEvent عمومی را ایجاد میکند که قرار است به همه شما و اصلاحکننده ورودی اشارهگر ارسال شود.
و این آخرین چیز در مرحله ما، مرحله ۶، در نهایت ارسال رویداد است.
این یک رویداد ورودی اشارهگر است که رویداد اتفاق افتاده است و به گره ارسال میشود تا هر کاری که میخواهد با آن انجام دهد.
این کار را در سه مرحله مختلف انجام میدهیم.
ما یک پاس اولیه، یک پاس اصلی و یک پاس نهایی داریم.
دلیل داشتن سه گذار مختلف این است که به گرهها اجازه میدهد با یکدیگر ارتباط برقرار کنند و علاقه خود را به بخشهای خاصی از رویدادهای ورودی اشارهگر خاص اعلام کنند، و سپس بخشهای مختلف آنها را بهعنوان بخشی از ارسال مصرف کنند.
بنابراین، اولین پاس، پاس اولیه، از جد به نواده میرود.
نمونهای از جایی که این مفید است زمانی است که برخیاز گرههای چیدمان بیرونی ممکن است محفظه پیمایشی باشند و بدانند که پیمایش را شروع کردهاند و بخواهند از رسیدن همه رویدادهای اشارهگر به هریک از فرزندانشان جلوگیری کنند.
این کار، در مرحله اولیه، به آن اجازه میدهد تا آن را مصرف کند، و سپس فرزندان دیگر هیچ رویدادی دریافت نخواهند کرد.
کارت دوم کارت اصلی است.
و این تنها موردی است که از فرزند به والد میرود.
همانطور که از نامش پیداست، این مجوز اصلی برای انجام کارها است.
برای مثال، این جایی است که یک فرزند رویداد پایین را مصرف میکند، شروع به مدیریت یک کلیک میکند، و سپس فرصتی برای انجام این کار قبل از هر یک از والدین خود دارد اگر عناصر قابل کلیک تودرتو داشته باشید.
و سپس آخرین گذر، گذر نهایی، شبیه به گذر اولیه است، جایی که از جد به نواده میرود.
و این جایی است که کودک میتواند به ولی بگوید که ولی بخشی از رویداد را مصرف کرده است.
بنابراین، شاید اگر کودکی شروع به انجام کلیک کند، اما سپس ولی شروع به پیمایش کند، کودک میتواند ببیند که ولی این کار را انجام داده است و سپس کودک میتواند کلیکی را که شروع به انجام آن کرده بود لغو کند.
بنابراین، این سه مرحله و این شش فاز است.
اینها این پایه و اساس همه چیز در بالای سیستم ورودی اشارهگر است.
همه مدیریت اشارهها، پیمایشپذیر، کشیدنی، کلیککردنی، و هر ورودی اشارهگر سفارشی بر روی این پایه ساخته شدهاند.
بنابراین، با همه اینها، اکنون میتوانیم کمی درباره جزئیات نحوه بهبود پشتیبانی از رفتار پد لمسی در Compose صحبت کنیم.
بنابراین، قبلاز Compose 1.12، رویدادهای ورودی پد لمسی بهعنوان نوع اشارهگر لمسی گزارش میشدند، زیرا انگشت بهصورت فیزیکی با پد لمسی برای کنترل مکاننما تعامل دارد.
بااینحال، انتظار کاربر این است که این نوع ورودی بسیار بیشتر شبیه موشواره فیزیکی عمل کند.
بنابراین، نوع اشارهگر را از PointerType.Touch به PointerType.Mouse تغییر دادیم.
با این تغییر، کاربران تجربه بسیار هماهنگتری از موشواره و پد لمسی برای هر مؤلفهای که به این اطلاعات نگاه میکند خواهند دید.
یک مثال در اینجا رفتار نکتهابزار است: احتمالاً میخواهید رفتار یکسانی برای پد لمسی و موشواره برای نکتهابزارها داشته باشید.
دومین تغییر بزرگی که در پد لمسی ایجاد کردیم مربوط به اشارههای چندانگشتی است که روی پد لمسی انجام میدهید.
پیشاز این، Compose از مفهوم هوشمندانه اما تاحدودی عجیب انگشتان جعلی در پلاتفرم Android عبور میکرد.
بنابراین، بیایید ببینیم این به چه معنا است.
وقتی از دو انگشت روی پد لمسی برای حرکت دادن استفاده میکنید، Android یک انگشت جعلی تکی روی صفحه لمسی ایجاد میکند که در جایی که مکاننما قرار دارد پایین میرود.
سپس، وقتی انگشتانتان را روی پد لمسی حرکت میدهید، انگشت مجازی روی صفحه لمسی بهاندازه و در جهت درست حرکت میکند تا رفتار موردنظر را تقلید کند، و سپس رها میشود.
بنابراین، حرکت دو انگشت بهسمت بالا روی پد لمسی به یک انگشت جعلی روی صفحه لمسی ترجمه میشود که آن هم بهسمت بالا حرکت میکند و درنتیجه باعث پیمایش بهسمت پایین میشود.
نکته جالب این است که اگر شما هم مثل من از ویژگی پیمایش معکوس پد لمسی استفاده میکنید، میخواهید پیمایش در جهت مخالف انجام شود.
و راهی که به آن دست مییابیم، برگرداندن جهت حرکت انگشت مصنوعی است.
بنابراین اکنون، دو انگشت روی پد لمسی که بهسمت بالا حرکت میکنند، به یک انگشت جعلی روی صفحه لمسی ترجمه میشوند که بهسمت پایین حرکت میکند، که بهنوبه خود باعث پیمایش بهسمت بالا میشود، یعنی برعکس آنچه قبلاً داشتیم.
مقیاسبندی هم تقریباً به همین شکل کار میکند.
وقتی از دو انگشت روی پد لمسی برای مقیاسبندی استفاده میکنید، Android دو انگشت مجازی روی صفحه لمسی ایجاد میکند که پایین میروند و سپس بسته به اشاره، به هم نزدیکتر یا از هم دورتر میشوند تا رفتار موردنظر برای بزرگنمایی یا کوچکنمایی را تقلید کنند.
بنابراین، از Compose نسخه ۱.۱۲ برای حرکت افقی و Compose نسخه ۱.۱۳ برای مقیاسبندی، اکنون این انگشتهای جعلی را رهگیری میکنیم.
ما آنها را همانطور که پلاتفرم به ما میدهد ارسال نمیکنیم، بلکه آنها را به انواع جدید رویداد اشارهگر تفسیر میکنیم: PanStart، PanMove، و PanEnd برای حرکت افقی، و ScaleStart، ScaleChange، و ScaleEnd برای مقیاسبندی.
And we do this in that Android-to-Compose event conversion phase that I talked about earlier.
بنابراین، رویدادهای «انگشت جعلی» را به انواع مناسب آنها تبدیل میکنیم و سپس این اشارههای تعریفشده در پلاتفرم را به «مسیر ضربه» که با مکاننما مطابقت دارد ارسال میکنیم.
و بیایید ببینیم چرا این موضوع اهمیت دارد.
اصلاً چرا این کار را میکنیم؟
بنابراین، فرض کنیم این کادر در اینجا یک تصویر زیبا است که میخواهیم بتوانیم آن را بزرگ کنیم.
وقتی از دو انگشت روی پد لمسی برای مقیاسبندی استفاده میکنید، ممکن است یکی از دو انگشت مجازی عنصر را ازدست بدهد.
Hit Path آن با تصویر تلاقی نمیکند و از خارج مرزها شروع میشود.
بنابراین، آنچه بهجای مقیاسبندی اتفاق میافتد این است که شاید تصویر پیمایش شود.
سپس شاید اگر عنصر پیمایشپذیر دیگری در خارج از تصویر وجود داشت، شاید آن عنصر هم با آن شروع به پیمایش میکرد.
این چیزی نیست که کاربر میخواست.
آنها میخواستند مقیاس را تغییر دهند.
بنابراین، کاری که اکنون انجام میدهیم این است که بهجای ارسال ازطریق آن انگشتان جعلی، اکنون اشاره مقیاسبندی را فقط به عنصر زیر موقعیت مکاننما ارسال میکنیم که سپس منجر به رفتار صحیح میشود.
مهم نیست مکاننما کجا باشد یا چقدر به لبه عنصری که میخواهید مقیاسبندی کنید نزدیک باشد، اکنون بهدرستی مقیاسبندی میشود.
بنابراین، خبر خوب برای همه شما این است که این رویدادهای ورودی بازتفسیرشده بهطور پیشفرض در مؤلفههایی مانند قابلپیمایش و قابلتبدیل استفاده میشوند تا درصورت استفاده از آن بلوکهای ساختمانی پایه، رفتار با کیفیت بالاتری برای ورودیهای پد لمسی ارائه دهند.
و اگر از چیزی استفاده میکنید که روی آنها ساخته شده است، فقط با بهروزرسانی نسخه Compose خود به جدیدترین نسخه، آن رفتار بهبودیافته را دریافت خواهید کرد.
و اگر درحال نوشتن اصلاحکنندههای ورودی اشارهگر سفارشی هستید، توصیه میکنم به این میاناهای برنامهسازی کاربردی جدید نگاهی بیندازید و مطمئن شوید که از آنها پشتیبانی میکنید تا بهترین تجربه ممکن را در همه این دستگاههای ورودی مختلف به کاربران خود ارائه دهید.
و البته، میاناهای برنامهسازی کاربردی آزمایشی را نیز اضافه کردیم تا بتوانید بررسی کنید که برنامهتان همه این انواع ورودی مختلف را بهدرستی مدیریت میکند.
بنابراین، performTrackpadInput بسیار شبیه به میاناهای برنامهسازی کاربردی آزمایشی موجود برای موشواره و لمس است.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}و بااستفاده از همه آنها میتوانید تأیید کنید که برنامهتان بهدرستی کار میکند، صرفنظر از اینکه کاربر از چه دستگاه ورودیای در دستگاهتان استفاده میکند.
بنابراین، امیدوارم این به شما ایده بسیار بهتری درباره نحوه حرکت یک رویداد اشارهگر از انگشت روی صفحه لمسی تا برنامه شما بدهد.
And, I'm now going to pass it over to Jeremy to talk about a very different way that trackpads are being used.
سپاسگزاریم.
بسیار خوب.
سلام، من جرمی واکر هستم.
من خیلی روی ورودی کار میکنم و فکر کردم جالب است اگر برخیاز چیزهای جدیدی را که با ورودی Compose انجام میدهیم و نحوه تأثیر آن بر شما را برایتان بگویم.
و بهطور خاص با این نوع ورودی غیرمستقیم که بهدلیل ورودی جدیدی براساس یک فرم فاکتور جدید بهوجود آمده است: افزاردید هوشمند.
بنابراین، وقتی از عینک ورودی دریافت میکنید، میتوانید این کار را به چند روش انجام دهید: میتوانید به چیزی نگاه کنید، میتوانید از صدای خود استفاده کنید، میتوانید از حرکات استفاده کنید، اما من بهطور خاص درباره صفحه لمسی صحبت خواهم کرد.
و اینها کمی با آنهایی که الکس دربارهشان صحبت کرد متفاوت هستند، از این جهت که مثلاً در کنار سر شما قرار دارند، عمودی هستند، بسیار پهن هستند، و بسیار کوتاه هستند.
بنابراین، وقتی داشتیم فکر میکردیم که این در Compose چگونه کار میکند، اگر تصور کنید که دارید به یک دکمه کوچک در مقابل خود نگاه میکنید، اگر مکاننما را حرکت دهید تا آن را لمس کنید، تجربه خوبی نخواهد بود، درست است؟
پس چطور میخواهیم از آن پشتیبانی کنیم؟
خب، این همان چیزی است که بهعنوان یادآوری، این همان چیزی است که واسط کاربری Glasses به نظر میرسد.
این دستگاه بهطور منحصربهفردی طراحی شده است تا محتوا را بهطور یکپارچه روی میدان دید شما قرار دهد.
اما خیلی ساده است، مثلاً، سعی میکنیم آن را ساده نگه داریم.
و اگر به نمونهای در اینجا نگاه کنیم، میتوانید ببینید که این یک فهرست است.
روی عنصر خاصی در مرکز متمرکز شده است.
واقعاً میخواهیم چه کاری انجام دهیم؟
خب، میخواهیم آن را انتخاب کنیم، یا شاید به قبلی یا بعدی برویم.
لزوماً به مکان صفحه نیاز نداریم.
و این یکی کمی پیچیدهتر است، اما میتوانید ببینید که اساساً سه گزینه وجود دارد: میتوانید قطع کنید، میتوانید صدادار کنید، یا میتوانید ویدیو را خاموش کنید.
اما درواقع، میخواهید آن را انتخاب کنید، یا به مورد دیگری بروید و آن را انتخاب کنید.
بنابراین، فکر میکنیم واقعاً نیازی نیست مستقیماً به مکانی روی صفحهنمایش، یا در این مورد، به چشمان شما نگاشت کنیم.
بله، این چیزی است که درواقع بهنظر میرسد.
بنابراین، برای مثال، میتوانید اینجا ببینید، من فقط بین عناصر حرکت میکنم، و این در واسط کاربر Glasses است، و این نوعی با آنچه میخواهیم مطابقت دارد.
و میتوانید روی آن ضربه بزنید.
اکنون، بنابراین، ما به Android نگاه کردیم تا فکر کنیم، چه چیزی در آنجا وجود دارد که میتوانیم از آن برای پشتیبانی از این استفاده کنیم.
And if you guys have ever if you get Oh, sorry.
اینجا رد کردن و جلو رفتن است.
بگذارید برگردم.
بفرمایید.
بسیار خوب.
بنابراین، اگر تابهحال از یکی از این دستگاهها استفاده کردهاید و آن را به یک دستگاه Android پیوند دادهاید، میدانید که میتوانید بهراحتی از عناصر میانای کاربر یکی به دیگری بروید.
برای مثال، با کنترل ازراهدور، میتوانید ببینید که میتوانید اگر تصور کنید یا آن را به Google TV پیوند داده باشید، و یک شبکه کامل از فیلمها داشته باشید، میتوانید دکمههای جهتنما را فشار دهید تا به فیلم موردنظرتان بروید، و میتوانید دکمه را فشار دهید تا آن را انتخاب کنید، دکمه تأیید.
اگر منو بازی دارید و این کنترلکننده را دارید، میتوانید از پد کنترل یا دسته فرمان برای رفتن به عنصر میانای کاربر، مثلاً در منو، استفاده کنید و آن را انتخاب کنید.
با صفحهکلید هم همینطور است.
بنابراین، این نوع با آنچه در Compose میخواهیم مطابقت دارد.
و دوباره، این چیزها هستند، بنابراین میتوانید نوعی ایده از ظاهر آن داشته باشید.
و اگر کمی بیشتر به عقب برگردیم، درواقع این نوع صفحه لمسی وجود داشت که با تلویزیون جفت میشد و درواقع از چیزی بهنام لمس پیمایشی استفاده میکرد.
و ایده این است که مختصات x و y کاملاً لمس نمیشوند و اصلاً به صفحه گره نخوردهاند.
این دادهها فقط برای تشخیص اشاره استفاده میشوند.
بنابراین، کاری که در آنجا انجام میدهید این است که اگر تند بهراست بکشید، میتوانید تصور کنید که این کار مانند فشار دادن دکمه پسندیدن است، پیکان برای رفتن به آنجا.
اگر تند بهچپ بکشید، به چپ، بالا، و پایین میروید.
بنابراین، این نوع تقریباً بیشتر آنچه را که میخواهیم به ما میدهد.
شما فقط از x و y برای ردیابی سرعت استفاده میکنید تا آن کشیدنها و تند کشیدنها و غیره را بفهمید، بنابراین، این نوعی به ما بیشتر آنچه را که میخواهیم برای شروع Compose به ما میدهد.
دقیق نیست چون بهنوعی به تلویزیون نگاشت شده است، بنابراین باید مطمئن شویم که مسیر تلویزیون را نمیرویم، بعداً تلویزیونها را خراب نمیکنیم، بنابراین باید مطمئن شویم که آنها را تقسیم میکنیم، اما این چیزی است که کمی بعد به آن خواهیم رسید.
اما این به ما یک نقطه شروع میدهد.
تا بتوانیم از این نوع پیمایش لمسی استفاده کنیم.
بسیارخوب.
طراحی میانای برنامهسازی کاربردی چگونه خواهد بود؟
خب، در Compose، عمومیترین یا رایجترین ورودی لمس است، درست است؟
بنابراین، کارهای زیادی در این زمینه داریم.
بنابراین، باید بهنوعی متوجه میشدیم که این موضوع چه ارتباطی با آن دارد.
بنابراین، برای یادآوری، PointerEvent به این شکل است.
در اینجا میتوانید ببینید که نوعی را دارید که نوع حرکت پایین در بالا است، و سپس این PointerInputChange را دارید که فقط یک فهرست است. میتوانید آن را بهعنوان انگشتان درنظر بگیرید.
اگر سه انگشتتان پایین باشد، سه مورد در فهرست خواهید داشت.
بنابراین میخواستیم خیلی شبیه این باشد.
اکنون، این یک ماژول واسط کاربر نیست که ماژول بسیار پایینتری نسبتبه آنچه اکثر توسعهدهندگان هنگام سروکار داشتن با ورودی استفاده میکنند باشد.
آنها لایهای بالاتر از لایه پایه خواهند بود.
و اینجاست که همه آشکارسازهای اشاره ما قرار دارند.
چون بهعنوان توسعهدهنده، به همه اینها مثل ورودی دقیق اهمیت نمیدهید.
فقط میخواهید بدانید که آیا کشیدن، کلیک کردن، یا تند کشیدن اتفاق افتاده است یا نه، و سپس کنش خود را براساس آن انجام میدهید.
بنابراین، ما کدهای زیادی داریم که این کار را برای شما انجام میدهند و این کدها در پسزمینه توسط این موارد پشتیبانی میشوند.
بنابراین، ما شروع به فکر کردن میکنیم، خوب، ما باید آن را تا حد امکان آسان کنیم تا کد موجود در پایه بتواند به راحتی از این انواع جدید پشتیبانی کند.
بنابراین، نوع جدیدی ایجاد کردیم که تقریباً مشابه است.
فقط به آن غیرمستقیم میگویند.
و غیرمستقیم یعنی لمس مستقیم روی صفحهنمایش نیست.
بنابراین، به شما اطلاع میدهد که، هی، این درحال آمدن است. از دیدگاه لمسی به این موضوع فکر نکنید.
فکر کنید که ما در اینجا فقط درحال مدیریت اشارهها و پیمایش بین عناصر میانای کاربر هستیم.
نوع یکسان است.
فهرستی از انگشتان دارد.
در این مورد، IndirectPointerInputChange است.
اما ما این کارها را انجام دادیم و نتیجه عالی بود.
اما بعد به مورد دیگری برخوردیم که «محور حرکت جهتدار اصلی» نام دارد.
این یک کلمه طولانی است، اما وقتی آن را توضیح دهم، منطقی خواهد بود، زیرا نحوه شکلگیری آن جالب بود.
پس چطور این اتفاق افتاد؟
خب، دوباره، شما این صفحه لمسی را در کنار سر خود دارید، و آن پهن و کوتاه است.
و بنابراین، در این مرحله، ما این ایده را داشتیم که، خوب، ما ناوبری لمسی داریم.
بنابراین، میتوانیم بهنوعی تند به چپ و راست بکشیم.
و این خوب کار میکند.
اما اگر به این فکر کنید که پدهای لمسی اکنون چگونه وجود دارند، درست است؟
وقتی به چپ یا راست میروید، در پد لمسی با محتوای میانای کاربر به چپ یا راست میروید.
اگر انگشتتان را بهبالا یا پایین بکشید، در فهرست بهبالا یا پایین پیمایش میکنید.
بنابراین اگر بخواهید این کار را با عینک انجام دهید، مثلاً این خیلی بد نیست، درست است؟
اما اگر این کار را انجام دهید، تجربه خوبی نخواهد بود.
بنابراین، چیزی که درواقع متوجه شدیم این است که طبیعیترین حرکت، بدیهی است که ضربه زدن است، اما تند کشیدن در امتداد کنار عینک نیز طبیعی است.
بنابراین ما مطالعات زیادی با کاربران انجام دادیم و آنها درواقع انتظار دارند که وقتی تند بهسمت افقی میکشید، محتوا را بهصورت افقی حرکت ندهید، بلکه آن را بهصورت عمودی حرکت دهید.
و اگر این را در ذهنتان امتحان کنید، میتوانید به آن فکر کنید، مثلاً وقتی اینجا تند میکشید، اگر فهرستی در مقابلتان باشد، مثل رفتن به جلو، میتوانید در فهرست به جلو بروید یا میتوانید در فهرست به عقب برگردید.
بنابراین، ما یک ورودی دو بعدی را میگیریم و سعی میکنیم به نوعی به آن به صورت یک بعدی فکر کنیم.
و این ایده برای «محور حرکت اصلی» از اینجا آمد که میتوانید بهصورت افقی حرکت کنید و بهصورت عمودی حرکت خواهد کرد.
بنابراین، میتوانید در خود دستگاه مشخص کنید.
میتوانید بگویید، هی، این یک پد لمسی معمولی نیست.
میخواهم حرکت کنم میخواهم جهت اصلی افقی باشد، و این نوعی محور اصلی، تنها محور واقعی، است که میخواهم در امتداد آن حرکت کنم.
و اینگونه بود که این ایده شکل گرفت.
بسیارخوب.
بنابراین، احساس خوبی نسبت به آن داریم.
Compose همه چیز را مدیریت میکند.
و این پیادهسازی عمودی خوب را دارید.
میتوانید به این موضوع فکر کنید.
بنابراین، نحوه نگاشت آن به این صورت است که اکنون با تعریف افقی، فقط یک جلو و عقب داریم.
ما بالا، پایین، چپ، و راست نداریم.
فقط جلو و عقب داریم.
و سپس، اگر با تمرکز با بعدی و قبلی آشنا باشید، میتوانیم آن را نگاشت کنیم.
بنابراین، اکنون فقط به بعدی و قبلی میرویم.
و این برای ویدیوهای عمودی عالی است.
و حتی بهتر هم هست.
با افقی آماده استفاده کار میکند، درست است؟
چون فقط یک دسته از موارد متمرکز دارید.
فقط به بعدی یا قبلی میروید و با هر دو کار میکند.
بنابراین این موردی با Compose است که ما به نوعی همه اینها را در Compose تعریف کردیم و فهمیدیم، اما واقعاً خوب میشد اگر خود دستگاه میتوانست بگوید، چون میداند که آیا شکل عجیبی دارد یا نه، و میداند که آیا فقط این محور اصلی را میخواهد یا نه، اما هیچ راهی برای تعریف آن ندارد.
بنابراین، اکنون باید این مفهوم را به پلاتفرم برمیگرداندیم و میگفتیم، بسیار خوب، باید این را در پلاتفرم تعریف کنیم تا بتوانیم آن را استانداردسازی کنیم و خود این دستگاهها بتوانند آن را مشخص کنند، تا Compose بتواند بهطور کامل از آن پشتیبانی کند.
بنابراین، موردی وجود دارد که ما بهنوعی در Compose کار کردیم و در Compose جلو رفتیم و به عقب برگشتیم و بهنوعی آن را با خودمان آوردیم.
بنابراین دقیقاً همین کار را کردیم.
این میانای برنامهسازی کاربردی جدید را ایجاد کردیم تا شما یا دستگاهتان بتوانید آن را مشخص کنید.
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()و اگر یادتان باشد، وقتی قبلاً درباره ورودی لمسی یا پیمایش لمسی صحبت کردم، گفتم که این ویژگی عمدتاً برای تلویزیونها طراحی شده است.
خب، ما نمیخواستیم آن پیادهسازیها را خراب کنیم.
بنابراین، فیلد دیگری برای مشخص کردن این نوع جدید ورودی شیشه اضافه کردیم.
بسیارخوب.
بنابراین، در نهایت، این برای شما چه معنایی دارد؟
خب، این نسخه خاصتری از چیزی است که بهعنوان تشخیصدهنده اشاره دریافت میکنید.
// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()اگر از Foundation استفاده میکنید، بهطور پیشفرض با تشخیصدهندههای اشاره کار خواهد کرد.
ما این کار را برایتان انجام دادیم، بنابراین لازم نیست کاری انجام دهید.
اما اگر میخواهید برای عینک برنامه بسازید، میتوانید از کتابخانه Glimmer ما استفاده کنید، و سپس ما یک آشکارساز اشاره خاص داریم، که فقط به شما میدهد، میدانید، بنابراین لازم نیست آنها را ترکیب کنید، میتوانید فقط از این یکی استفاده کنید، کلیک، قاب جلو، و عقب را دریافت کنید.
بسیارخوب.
بنابراین، ما نحوه عملکرد API را در پلاتفرم و در Compose پوشش دادهایم.
آزمایش چطور؟
بنابراین، وقتی میخواهید آزمایش لمس انجام دهید، لمس به این شکل است.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}کافی است بگویید performTouchInput و سپس اشارهای انجام دهید.
اشارههای مختلف زیادی وجود دارد و ما همه رویدادهای کوچک را ایجاد میکنیم تا اشاره اتفاق بیفتد.
این کار در سطح بالا چگونه انجام میشود؟
خب، شما ماژول Compose UI را دارید، ماژول UI Testing را دارید و آنها به نوعی، حداقل از نظر ورودی، بااستفاده از MotionEvents ارتباط برقرار میکنند.
همچنین، واحد UI Testing از سازوکار صفبندی استفاده میکند که کاملاً براساس MotionEvent است.
چرا این را به شما میگویم؟
چون نمیتوانید بهصورت برنامهنویسیشده MotionEvent ایجاد کنید که مانند رویداد ورودی غیرمستقیم باشد، که برای من عالی بود چون به این معنی بود که باید تمام ارتباطات بین ماژولها و سیستم صفبندی را بازنویسی کنم.
ولی من این کار را کردم.
بنابراین، این برای شما به چه معنا است؟
خب، اگر به آزمایش ورودی لمسی عادت دارید، این چیزی است که برای غیرمستقیم دریافت میکنید.
فقط غیرمستقیم را به نام اضافه میکنید.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}اکنون، من این کار را کردم، من چند پارامتر اضافی را ارسال میکنم.
احتمالاً اولی را میشناسید، جایی است که خط افقی را مشخص میکنید.
و سپس اندازه ورودی بدیهی است که اندازه صفحه لمسی خواهد بود.
اکنون، این تغییر چندانی نمیکند.
بنابراین، اگر به کد منبع نگاه کنید، میتوانید ببینید که من فقط یک تابع افزونه نوشتم و فقط اینگونه بهنظر میرسد.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}بسیارخوب.
بنابراین، این به شما، این باید به شما ایدهای از تمام کارهایی که فقط برای پشتیبانی از پدهای لمسی روی قابهای شما یا روی افزاردید هوشمند انجام دادیم بدهد.
اما ما این کارها را انجام میدهیم، من این تمرینات را برای ردیابی چشم و دست نیز انجام میدهم، بنابراین چیزهای بیشتری در راه است.
و بنابراین، در یک نمای کلی، میخواهم چه چیزی را از بخش من بهخاطر بسپارید؟
بنابراین، چه از پد لمسی با تلفن یا عینک یا هر ورودی دیگری استفاده کنید، ما به آن فکر میکنیم و سعی میکنیم کار را ازقبل انجام دهیم.
بنابراین، اگر از این آشکارسازهای اشاره Foundation استفاده میکنید، این ویژگی بهطور پیشفرض کار میکند.
این شماره یک است.
مورد دوم، Compose جعبهابزار اصلی واسط کاربر است و ما به کاوش و پشتیبانی از انواع ورودیهای جدید، هم در پلاتفرم و هم در جعبهابزار، ادامه خواهیم داد، که میتوانید ببینید من در اینجا همین کار را انجام دادم.
و سپس، بدیهی است که برای آزمایش میاناهای برنامهسازی کاربردی، میخواهم سطح و الگوی آشنایی را حفظ کنم تا انتقال و آزمایش این انواع جدید برای شما تا حد امکان آسان باشد.
متشکرم، امیدوارم مفید بوده باشد.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}