{"id":444269,"date":"2025-01-08T03:03:00","date_gmt":"2025-01-08T03:03:00","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=444269"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=444269","title":{"rendered":"<span>\u0410\u043b\u043b\u043e! \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0434\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0442\u0435\u043b\u0435\u0444\u043e\u043d \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Jetpack Compose(\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043e)<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u0410\u0440\u0442\u0435\u043c \u0438 \u044f \u0430\u0432\u0442\u043e\u0440 \u0438 \u0432\u0435\u0434\u0443\u0449\u0438\u0439 <a href=\"https:\/\/www.youtube.com\/@Android_Insights\" rel=\"noopener noreferrer nofollow\">YouTube<\/a> \u0438 <a href=\"https:\/\/t.me\/android_insights\" rel=\"noopener noreferrer nofollow\">Telegram<\/a> \u043a\u0430\u043d\u0430\u043b\u043e\u0432\u00a0Android Insights.<\/p>\n<p>\u0414\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f &#8212; \u0438\u0434\u0435\u0439\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0435\u0439 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/867822\/\" rel=\"noopener noreferrer nofollow\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 Custom Layout \u0432 Jetpack Compose<\/a><\/p>\n<h2>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0423\u0436\u0435 \u043e\u0442\u0433\u0440\u0435\u043c\u0435\u043b\u0438 \u043f\u0440\u0430\u0437\u0434\u043d\u0438\u0447\u043d\u044b\u0435 \u0441\u0430\u043b\u044e\u0442\u044b, \u0437\u0430\u043f\u0430\u0441\u044b \u0441\u0430\u043b\u0430\u0442\u043e\u0432 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0442 \u043a \u043a\u043e\u043d\u0446\u0443, \u044f \u0442\u0438\u0445\u043e \u0441\u0438\u0434\u0435\u043b \u0434\u043e\u043c\u0430, \u043f\u0440\u0435\u0434\u0430\u0432\u0430\u044f\u0441\u044c \u043d\u043e\u0441\u0442\u0430\u043b\u044c\u0433\u0438\u0438 \u043e \u0431\u044b\u043b\u044b\u0445 \u0432\u0440\u0435\u043c\u0435\u043d\u0430\u0445. \u041d\u043e \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0442\u0435\u0441\u0442. \u0417\u043d\u0430\u0435\u0442\u0435 \u043b\u0438 \u0432\u044b, \u0447\u0442\u043e \u0437\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0438 \u043d\u0438\u0436\u0435?<\/p>\n<details class=\"spoiler\">\n<summary>\u041e\u043b\u0434\u044b \u043d\u0430 \u043c\u0435\u0441\u0442\u0435?<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7e4\/ca1\/367\/7e4ca136772fcb094e7439a29b4d17eb.png\" width=\"800\" height=\"600\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7e4\/ca1\/367\/7e4ca136772fcb094e7439a29b4d17eb.png\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u0430, \u043f\u043e \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043f\u0440\u0438\u0447\u0438\u043d\u0435 \u043c\u043d\u0435 \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043b\u0441\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0435\u043b\u0435\u0444\u043e\u043d, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u044f \u043c\u043e\u0433 \u0434\u043e\u043b\u0433\u043e \u0440\u0430\u0437\u0433\u043e\u0432\u0430\u0440\u0438\u0432\u0430\u0442\u044c \u0441 \u0434\u0440\u0443\u0437\u044c\u044f\u043c\u0438 \u0432 \u0434\u0435\u0442\u0441\u0442\u0432\u0435. <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0431\u043b\u0438\u0436\u0435 \u043a \u0434\u0435\u043b\u0443, \u0443 \u0442\u0430\u043a\u0438\u0445 \u0441\u0442\u0430\u0440\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432 \u043e\u0447\u0435\u043d\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430\u044f \u043c\u0435\u0445\u0430\u043d\u0438\u043a\u0430 \u043d\u0430\u0431\u043e\u0440\u0430 \u043d\u043e\u043c\u0435\u0440\u0430, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e &#8212; \u043d\u0443\u0436\u043d\u043e \u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0434\u0438\u0441\u043a. \u0418 \u043c\u043d\u0435 \u0432 \u0433\u043e\u043b\u043e\u0432\u0443 \u043f\u0440\u0438\u0448\u043b\u0430 \u0438\u0434\u0435\u044f \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0435\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c.<\/p>\n<h2>\u0411\u0430\u0437\u043e\u0432\u044b\u0439 UI<\/h2>\n<p>\u0415\u0441\u043b\u0438 \u0432 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u043b \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 Layout, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u0435\u0431\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043a \u043d\u0438\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u0430, \u0442\u043e \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u044f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <strong>Canvas<\/strong>, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0436\u0435\u0441\u0442\u043e\u0432.<\/p>\n<p>\u041b\u044e\u0431\u043e\u0435 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432\u043e\u043a\u0440\u0443\u0433 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043e\u0441\u0438, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u043e\u0441\u044c \u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430. \u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u0445\u0435\u043c\u0430 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/715\/1d0\/a50\/7151d0a505d89fff5563fcb253627934.png\" width=\"1080\" height=\"1920\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/715\/1d0\/a50\/7151d0a505d89fff5563fcb253627934.png\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c, \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c\u043e \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 \u0434\u0438\u0441\u043a \u043d\u0430\u0431\u043e\u0440\u0430 \u043d\u043e\u043c\u0435\u0440\u0430<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0440\u0430\u0432\u043d\u043e\u043c\u0435\u0440\u043d\u043e \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u0446\u0438\u0444\u0440\u044b \u043d\u0430 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438, \u044f \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e \u0435\u0451 \u043a\u0430\u043a \u043a\u0440\u0443\u0433 \u0441 \u0446\u0435\u043d\u0442\u0440\u043e\u043c. \u0414\u043b\u044f \u0440\u0430\u0432\u043d\u043e\u043c\u0435\u0440\u043d\u043e\u0433\u043e \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0446\u0438\u0444\u0440 \u043f\u043e \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u043a\u0440\u0443\u0433\u0430 \u043d\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0446\u0438\u0444\u0440.<\/p>\n<p>\u0423 \u043d\u0430\u0441 10 \u0446\u0438\u0444\u0440, \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u043a\u0440\u0443\u0433\u0430 \u043a\u0430\u0436\u0434\u0430\u044f \u0437\u0430\u0439\u043c\u0451\u0442 \u0443\u0433\u043e\u043b(<em>\u043f\u043e\u043b\u043d\u044b\u0439 \u043a\u0440\u0443\u0433 &#8212; 360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432<\/em>)<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"angleStep =\\frac{360}{10}= 36\" alt=\"angleStep =\\frac{360}{10}= 36\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9d9\/284\/981\/9d9284981b23289be6b3d67b0f79a93d.svg\" width=\"193\" height=\"43\"\/><\/p>\n<p>\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0432 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0434\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432, \u044f \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b, \u0447\u0442\u043e \u0443 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u0446\u0438\u0444\u0435\u0440\u0431\u043b\u0430\u0442 \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 3 \/ 4 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430 \u043d\u0430 \u043a\u0440\u0443\u0433\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0448\u0430\u0433 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0446\u0438\u0444\u0440 \u0431\u0443\u0434\u0435\u0442 \u0447\u0443\u0442\u044c \u043c\u0435\u043d\u044c\u0448\u0435<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"angleStep =\\frac{270}{10}= 27\" alt=\"angleStep =\\frac{270}{10}= 27\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a35\/927\/113\/a35927113592e83c7a369db9430a3643.svg\" width=\"193\" height=\"43\"\/><\/p>\n<p>\u041f\u043b\u044e\u0441 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0435 \u0437\u0430\u0431\u044b\u0442\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0448\u0430\u0433 \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u043b\u0438\u0441\u044c \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438<\/p>\n<p>\u0426\u0438\u0444\u0440\u044b \u0440\u0430\u0437\u043c\u0435\u0449\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0443\u0433\u043e\u043b \u0438 \u0440\u0430\u0434\u0438\u0443\u0441. \u042d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0442\u0440\u0438\u0433\u043e\u043d\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"x=centerX+radius\u22c5cos(angle)\" alt=\"x=centerX+radius\u22c5cos(angle)\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d0d\/ebe\/bff\/d0debebffdf7543c0d50b1abb2a43a02.svg\" width=\"292\" height=\"22\"\/><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"y=centerY+radius\u22c5sin(angle)\" alt=\"y=centerY+radius\u22c5sin(angle)\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/102\/634\/ad3\/102634ad342924685b891ff6cec85e53.svg\" width=\"289\" height=\"22\"\/><\/p>\n<p>\u0417\u0434\u0435\u0441\u044c:<\/p>\n<ul>\n<li>\n<p>centerX, centerY \u2014 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0446\u0435\u043d\u0442\u0440\u0430 \u043a\u0440\u0443\u0433\u0430<\/p>\n<\/li>\n<li>\n<p>radius \u2014 \u0440\u0430\u0434\u0438\u0443\u0441 \u043a\u0440\u0443\u0433\u0430<\/p>\n<\/li>\n<li>\n<p>angle \u2014 \u0443\u0433\u043e\u043b \u0432 \u0440\u0430\u0434\u0438\u0430\u043d\u0430\u0445<\/p>\n<\/li>\n<\/ul>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u0441\u0438\u043d\u0443\u0441\u0430 \u0438 \u0441\u0438\u043d\u0443\u0441\u0430 \u0432 \u041a\u043e\u0442\u043b\u0438\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0441 \u0440\u0430\u0434\u0438\u0430\u043d\u0430\u043c\u0438, \u0430 \u043d\u0435 \u0441 \u0433\u0440\u0430\u0434\u0443\u0441\u0430\u043c\u0438. \u0427\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0443\u0433\u043e\u043b \u0438\u0437 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432 \u0432 \u0440\u0430\u0434\u0438\u0430\u043d\u044b, \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0444\u043e\u0440\u043c\u0443\u043b\u0443:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"angleInRadians=angleInDegrees\u22c5\\frac{180}{\u03c0}\" alt=\"angleInRadians=angleInDegrees\u22c5\\frac{180}{\u03c0}\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b94\/5f0\/5a1\/b945f05a1a0a98ac69a524bea039628c.svg\" width=\"357\" height=\"43\"\/><\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0446\u0438\u0444\u0435\u0440\u0431\u043b\u0430\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b \u0442\u0430\u043a, \u043a\u0430\u043a \u044f \u0445\u043e\u0442\u0435\u043b, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043d\u0430 -45 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u0424\u043e\u0440\u043c\u0443\u043b\u0430 \u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0446\u0438\u0444\u0440\u044b \u0432 \u043e\u0431\u0449\u0435\u043c \u0432\u0438\u0434\u0435:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"angleInDegrees=angleStart+index\u22c5angleStep\" alt=\"angleInDegrees=angleStart+index\u22c5angleStep\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0e5\/d58\/a92\/0e5d58a9263e53d817745bca6d8439d5.svg\" width=\"430\" height=\"21\"\/><\/p>\n<p>\u0410 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0442\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0432\u0438\u0434:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"angleInDegrees=index\u22c5angleStep-45\" alt=\"angleInDegrees=index\u22c5angleStep-45\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/46b\/476\/42a\/46b47642a60abe574c0d878ece42cd3c.svg\" width=\"357\" height=\"21\"\/><\/p>\n<p>\u0415\u0449\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0435 \u0437\u0430\u0431\u044b\u0442\u044c \u043e\u0442\u0446\u0435\u043d\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442, \u043d\u043e \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0441\u043e\u0432\u0441\u0435\u043c \u043b\u0435\u0433\u043a\u043e, \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0444\u043e\u0440\u043c\u0443\u043b\u044b:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"x_{text}=x-\\frac{width}{2}\" alt=\"x_{text}=x-\\frac{width}{2}\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a30\/aa9\/034\/a30aa90341bcd64f79a7ac1c750e7bbc.svg\" width=\"155\" height=\"43\"\/><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"y_{text}=y-\\frac{width}{2}\" alt=\"y_{text}=y-\\frac{width}{2}\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c99\/3c6\/422\/c993c642275d91ebb214842566157d6d.svg\" width=\"152\" height=\"43\"\/><\/p>\n<p>width, height \u2014 \u0440\u0430\u0437\u043c\u0435\u0440\u044b \u0442\u0435\u043a\u0441\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 TextMeasurer<\/p>\n<p>\u0418\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0441 \u0446\u0438\u0444\u0435\u0440\u0431\u043b\u0430\u0442\u043e\u043c \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">private val digits = listOf(\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"0\")  @Composable fun OldPhone(     modifier: Modifier, ) {     var rotationAngle by remember { mutableFloatStateOf(0f) }     val textMeasurer = rememberTextMeasurer()     val typography = MaterialTheme.typography.headlineLarge      Canvas(         modifier = modifier.aspectRatio(1f)     ) {         rotate(rotationAngle) {             drawCircle(                 color = Color.White,             )              val angleStep = -270f \/ digits.size             val center = Offset(size.width \/ 2, size.height \/ 2)             val radius = size.width \/ 2f - 20.dp.toPx()              digits.forEachIndexed { index, digit -&gt;                 val angleInDegrees = index * angleStep - 45                 val angleInRadians = Math.toRadians(angleInDegrees.toDouble())                  val x = center.x + radius * cos(angleInRadians).toFloat()                 val y = center.y + radius * sin(angleInRadians).toFloat()                  val textLayoutResult: TextLayoutResult = textMeasurer.measure(                     text = digit,                     style = typography,                 )                  drawText(                     textLayoutResult = textLayoutResult,                     topLeft = Offset(                         x - textLayoutResult.size.width \/ 2f,                         y - textLayoutResult.size.height \/ 2f,                     ),                 )             }         }     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0410 \u0432\u043e\u0442 \u0442\u0430\u043a \u0432\u0441\u0435 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442<\/p>\n<details class=\"spoiler\">\n<summary>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ead\/11a\/431\/ead11a431af26dc1ec03fa78578bba09.png\" width=\"1080\" height=\"1920\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ead\/11a\/431\/ead11a431af26dc1ec03fa78578bba09.png\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u0434\u0435\u043b\u0430\u043d\u043e \u043d\u0435 \u0442\u0430\u043a \u043c\u043d\u043e\u0433\u043e, \u043d\u043e \u0443\u0436\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u043e\u0445\u043e\u0436\u0435, \u043d\u0435 \u043f\u0440\u0430\u0432\u0434\u0430 \u043b\u0438?<\/p>\n<h2>\u0412\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u043e\u0433\u043e \u0434\u0438\u0441\u043a\u0430<\/h2>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0443\u0433\u043b\u0430 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u2014 \u0432\u0430\u0436\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0434\u0438\u0441\u043a\u0430 \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430. \u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u043b\u0438\u043d\u0438\u0435\u0439 \u043e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u043a\u0440\u0443\u0433\u0430 \u043a \u0442\u043e\u0447\u043a\u0435 \u043a\u0430\u0441\u0430\u043d\u0438\u044f \u043f\u0430\u043b\u044c\u0446\u0430 \u0438 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0441\u044c\u044e \u043a\u0440\u0443\u0433\u0430. \u042d\u0442\u043e\u0442 \u0443\u0433\u043e\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0441\u043a \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u043b\u0441\u044f.<\/p>\n<p>\u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<details class=\"spoiler\">\n<summary>\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/d0f\/bbd\/8bf\/d0fbbd8bf6e5bab96205bbd91e0c2c31.jpg\" width=\"500\" height=\"500\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d0f\/bbd\/8bf\/d0fbbd8bf6e5bab96205bbd91e0c2c31.jpg\" data-blurred=\"true\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u043f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u043a\u043e\u0434, \u0430 \u043f\u043e\u0442\u043e\u043c \u0440\u0430\u0437\u0431\u0435\u0440\u0443, \u0447\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u043d\u0435\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">@Composable fun OldPhone(     modifier: Modifier, ) {     var rotationAngle by remember { mutableFloatStateOf(0f) }      \/\/ \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0434      Canvas(         modifier = modifier             .aspectRatio(1f)             .pointerInput(Unit) {                 awaitEachGesture {                     val down = awaitFirstDown()                     var currentAngle = calculateAngle(down.position, size.center)                      do {                         val event = awaitPointerEvent()                         val newAngle = calculateAngle(event.changes.first().position, size.center)                         val deltaAngle = newAngle - currentAngle                         rotationAngle = (rotationAngle + deltaAngle)                         currentAngle = newAngle                         event.changes.first().consume()                     } while (event.changes.any { it.pressed })                 }             }     ) {         rotate(rotationAngle) {             \/\/ \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0434         }     } }  private fun calculateAngle(position: Offset, center: IntOffset): Float {     val dx = position.x - center.x     val dy = position.y - center.y     return Math.toDegrees(atan2(dy.toDouble(), dx.toDouble())).toFloat() }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u0430\u043a \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0443\u0433\u043e\u043b?<\/p>\n<p>\u041f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0438 \u043f\u0430\u043b\u044c\u0446\u0430 \u043c\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0440\u0430\u0437\u043d\u0438\u0446\u0443 \u043f\u043e \u043e\u0441\u044f\u043c X \u0438 Y:<\/p>\n<pre><code class=\"kotlin\">val dx = position.x - center.x \/\/ \u0421\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u043e X val dy = position.y - center.y \/\/ \u0421\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u043e Y<\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442 \u0432\u0435\u043a\u0442\u043e\u0440 \u043e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u043a\u0440\u0443\u0433\u0430 \u043a \u0442\u043e\u0447\u043a\u0435 \u043a\u0430\u0441\u0430\u043d\u0438\u044f. \u0421 \u044d\u0442\u0438\u043c \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u043c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0443\u0433\u043e\u043b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 atan2:<\/p>\n<pre><code class=\"kotlin\">val angleInRadians = atan2(dy.toDouble(), dx.toDouble())<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0443\u0433\u043e\u043b \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0435 \u043e\u0442 \u2212180 \u0434\u043e 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432.<\/p>\n<p>\u041a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0443\u0433\u043e\u043b?<\/p>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043f\u0430\u043b\u0435\u0446 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f, \u043c\u044b \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u0440\u0430\u0437\u043d\u0438\u0446\u0443 \u043c\u0435\u0436\u0434\u0443 \u043d\u043e\u0432\u044b\u043c \u0443\u0433\u043b\u043e\u043c \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u043c:<\/p>\n<pre><code class=\"kotlin\">val deltaAngle = newAngle - currentAngle rotationAngle += deltaAngle currentAngle = newAngle<\/code><\/pre>\n<ul>\n<li>\n<p>deltaAngle \u2014 \u044d\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0443\u0433\u043b\u0430, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0441\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 deltaAngle \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439, \u0434\u0438\u0441\u043a \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u2014 \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a \u043a\u0440\u0430\u0442\u043a\u043e \u043c\u043e\u0436\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c<\/p>\n<ol>\n<li>\n<p>\u0412 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u0430\u0441\u0430\u043d\u0438\u044f \u044d\u043a\u0440\u0430\u043d\u0430, \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439 \u0443\u0433\u043e\u043b<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0438 \u043f\u0430\u043b\u044c\u0446\u0430 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0439 \u0443\u0433\u043e\u043b<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0443\u0433\u043e\u043b \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p>\u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u0438\u0441\u043a\u0443 \u043f\u043b\u0430\u0432\u043d\u043e \u0432\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043f\u0430\u043b\u044c\u0446\u0430.<\/p>\n<\/li>\n<\/ol>\n<p>\u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0434\u0435\u043b\u0430\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0438\u043d\u0442\u0443\u0438\u0442\u0438\u0432\u043d\u044b\u043c \u0438 \u0442\u043e\u0447\u043d\u044b\u043c.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/p>\n<details class=\"spoiler\">\n<summary>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ac4\/b31\/163\/ac4b311635d169483ec3505e2d1246e6.gif\" width=\"800\" height=\"1422\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ac4\/b31\/163\/ac4b311635d169483ec3505e2d1246e6.gif\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u043d\u0435 \u0443\u0436\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f!<\/p>\n<h2>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u0436\u0430\u0442\u043e\u0439 \u0446\u0438\u0444\u0440\u044b \u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430<\/h2>\n<p>\u0421\u0435\u0439\u0447\u0430\u0441 \u0443 \u043d\u0430\u0448\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 &#8212; \u0434\u0438\u0441\u043a \u043c\u043e\u0436\u043d\u043e \u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0432 \u043b\u044e\u0431\u044b\u0445 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u0445 \u0431\u0435\u0437 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439, \u043d\u043e \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u0442\u0435\u043b\u0435\u0444\u043e\u043d \u0442\u0430\u043a \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c!<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c deltaAngle, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u0440\u0438 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0438 \u0440\u0430\u0437\u043d\u0438\u0446\u044b \u043c\u0435\u0436\u0434\u0443 \u0434\u0432\u0443\u043c\u044f \u0443\u0433\u043b\u0430\u043c\u0438, \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 180 \u0438\u043b\u0438 \u043c\u0435\u043d\u044c\u0448\u0435 \u2212180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432 \u0438\u0437-\u0437\u0430 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0447\u0435\u0440\u0435\u0437 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u2212180 \u0438 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u041d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0438\u0440\u0443\u0435\u0442 \u0442\u0430\u043a\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f:<\/p>\n<ul>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0440\u0430\u0437\u043d\u0438\u0446\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0432\u044b\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f 360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0443\u0433\u043e\u043b \u0432 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043c\u0435\u043d\u044c\u0448\u0435 \u2212180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f 360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"kotlin\">val deltaAngle = (newAngle - currentAngle).let { delta -&gt;     when {         delta &gt; 180f -&gt; delta - 360f         delta &lt; -180f -&gt; delta + 360f         else -&gt; delta     } }<\/code><\/pre>\n<p>\u0414\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0446\u0438\u0444\u0440 \u0440\u0430\u0432\u0435\u043d 270 \u0433\u0440\u0430\u0434\u0443\u0441\u0430\u043c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u0442 \u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430 \u0440\u0430\u0432\u043d\u044b\u043c\u0438 \u043e\u0442 -270 \u0434\u043e 270 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c, \u044f \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u0434\u0435\u043b\u044c\u0442\u0443 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435<\/p>\n<pre><code class=\"kotlin\">var accumulatedDelta by remember { mutableFloatStateOf(0f) }  val targetRotation = rotationAngle + deltaAngle  val canRotate = (accumulatedDelta &lt;= 270f &amp;&amp; deltaAngle &gt; 0) ||                 (accumulatedDelta &gt;= -270f &amp;&amp; deltaAngle &lt; 0) if (canRotate) {     rotationAngle = targetRotation     accumulatedDelta += deltaAngle }<\/code><\/pre>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u043d\u0430\u043a\u0430 deltaAngle \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430:<\/p>\n<ul>\n<li>\n<p>deltaAngle &lt; 0 &#8212; \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438<\/p>\n<\/li>\n<li>\n<p>deltaAngle &gt; 0 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0439\u043c\u0435\u043c\u0441\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043c \u0442\u043e\u0433\u043e, \u043a\u0430\u043a\u0430\u044f \u0446\u0438\u0444\u0440\u0430 \u0431\u044b\u043b\u0430 \u043d\u0430\u0436\u0430\u0442\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u044f \u0434\u043e\u0431\u0430\u0432\u043b\u044e \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 DigitBounds. \u042d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0433\u0440\u0430\u043d\u0438\u0446 \u043a\u0430\u0436\u0434\u043e\u0439 \u0446\u0438\u0444\u0440\u044b.<\/p>\n<pre><code class=\"kotlin\">private class DigitBounds(     var left: Float = 0f,     var top: Float = 0f,     var right: Float = 0f,     var bottom: Float = 0f ) {     fun contains(point: Offset): Boolean {         return point.x &gt;= left &amp;&amp; point.x &lt;= right &amp;&amp;                 point.y &gt;= top &amp;&amp; point.y &lt;= bottom     }      fun update(x: Float, y: Float, size: Float) {         left = x - size \/ 2         top = y - size \/ 2         right = x + size \/ 2         bottom = y + size \/ 2     } }<\/code><\/pre>\n<ul>\n<li>\n<p><strong>\u041c\u0435\u0442\u043e\u0434 <\/strong>contains:<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043b\u0438 \u0442\u043e\u0447\u043a\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u0433\u0440\u0430\u043d\u0438\u0446 \u0446\u0438\u0444\u0440\u044b<\/p>\n<\/li>\n<li>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043d\u0430\u0436\u0430\u0442\u043e\u0439 \u0446\u0438\u0444\u0440\u044b<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>\u041c\u0435\u0442\u043e\u0434 <\/strong>update:<\/p>\n<ul>\n<li>\n<p>\u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u0446\u0438\u0444\u0440\u044b \u043f\u0440\u0438 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0435, \u043e\u0441\u043d\u043e\u0432\u044b\u0432\u0430\u044f\u0441\u044c \u043d\u0430 \u0435\u0451 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 (x, y) \u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0430\u0445<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>\u0412\u0441\u0435 \u043f\u043e\u043b\u044f \u043a\u043b\u0430\u0441\u0441\u0430 DigitBounds &#8212; \u043c\u0443\u0442\u0430\u0431\u0435\u043b\u044c\u043d\u044b\u0435, \u044d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u0442 \u043a \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043c\u0443\u0441\u043e\u0440\u0430<\/p>\n<p>\u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u044f \u0441\u043e\u0437\u0434\u0430\u044e \u0441\u043f\u0438\u0441\u043e\u043a DigitBounds \u0440\u0430\u0432\u043d\u044b\u0439 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0443 \u0446\u0438\u0444\u0440<\/p>\n<pre><code class=\"kotlin\">val digitBoundsList = remember { List(digits.size) { DigitBounds() } }<\/code><\/pre>\n<p>\u0410 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u044e \u044d\u0442\u043e\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0443\u0436\u0435 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u043e\u0442\u0440\u0438\u0441\u043e\u043a\u0438<\/p>\n<pre><code class=\"kotlin\">digits.forEachIndexed { index, _ -&gt;     val angleInDegrees = index * angleStep - 45     val angleInRadians = Math.toRadians(angleInDegrees.toDouble())      val x = center.x + radius * cos(angleInRadians).toFloat()     val y = center.y + radius * sin(angleInRadians).toFloat()      digitBoundsList[index].update(x, y, digitSize) }<\/code><\/pre>\n<p>\u0421\u0430\u043c\u043e \u0436\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u0436\u0430\u0442\u043e\u0439 \u0446\u0438\u0444\u0440\u044b \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u043a\u0430\u0441\u0430\u043d\u0438\u0439<\/p>\n<pre><code class=\"kotlin\">val rotatedPoint = down.position.rotate(   center = size.center,   degrees = -rotationAngle, )  val digitHit = digitBoundsList.indexOfFirst { it.contains(rotatedPoint) }  if (digitHit != -1) {     \/\/ \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043a\u043e\u0434 }<\/code><\/pre>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c \u044f \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u044f \u043f\u0440\u0438\u0432\u043e\u0436\u0443 \u0442\u043e\u0447\u043a\u0443 \u043a\u0430\u0441\u0430\u043d\u0438\u044f \u043a \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c \u0434\u0438\u0441\u043a\u0430, \u0437\u0430\u0442\u0435\u043c \u043f\u0440\u043e\u0445\u043e\u0436\u0443\u0441\u044c \u043f\u043e \u0441\u043f\u0438\u0441\u043a\u0443 \u0438 \u043f\u044b\u0442\u0430\u044e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u043d\u0430\u0436\u0430\u0442\u0443\u044e \u0446\u0438\u0444\u0440\u0443, \u0435\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0432\u043e\u0439 \u043d\u0435\u0442, \u0442\u043e \u0432\u0435\u0440\u043d\u0435\u0442\u0441\u044f -1 \u0438 \u0436\u0435\u0441\u0442 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d. \u0421\u0430\u043c\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f rotate \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"kotlin\">fun Offset.rotate(     center: IntOffset,     degrees: Float, ): Offset {     val angle = Math.toRadians(degrees.toDouble())     val cos = cos(angle).toFloat()     val sin = sin(angle).toFloat()      val x = this.x - center.x     val y = this.y - center.y      return Offset(         x = center.x + (x * cos - y * sin),         y = center.y + (x * sin + y * cos)     ) }<\/code><\/pre>\n<p>\u0421\u043d\u043e\u0432\u0430 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0442\u0440\u0438\u0433\u043e\u043d\u043e\u043c\u0435\u0442\u0440\u0438\u0438 \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u0441\u0432\u0435\u0441\u0442\u0438\u0442\u044c \u0446\u0438\u0444\u0440\u0443, \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e\u043f\u0430\u043b \u043f\u043e \u043d\u0435\u0439. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u044f \u0431\u0443\u0434\u0443 \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0442\u044c \u0435\u0435 \u0438\u043d\u0434\u0435\u043a\u0441, \u0435\u0441\u043b\u0438 \u043e\u043d \u0431\u044b\u043b \u043d\u0430\u0439\u0434\u0435\u043d \u0438 \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u043f\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0436\u0435\u0441\u0442\u0430<\/p>\n<pre><code class=\"kotlin\">var activeDigitIndex by remember { mutableIntStateOf(-1) }  awaitEachGesture {   \/\/ \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043a\u043e\u0434    val digitHit = digitBoundsList.indexOfFirst { it.contains(rotatedPoint) }   activeDigitIndex = digitHit    if (digitHit != -1) {       \/\/ \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043a\u043e\u0434        activeDigitIndex = -1   } }<\/code><\/pre>\n<p>\u0418\u043d\u0434\u0435\u043a\u0441 \u043d\u0430\u0436\u0430\u0442\u043e\u0439 \u0446\u0438\u0444\u0440\u044b \u0431\u044b\u043b \u043d\u0430\u0439\u0434\u0435\u043d, \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440. \u0412 \u043c\u043e\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u044b\u0439 \u0441\u0438\u043d\u0438\u0439 \u043a\u0440\u0443\u0433. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0440\u0438\u0441\u0443\u0435\u043c \u0435\u0433\u043e.<\/p>\n<pre><code class=\"kotlin\">digits.forEachIndexed { index, digit -&gt;     \/\/ \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0434      val isActive = index == activeDigitIndex      if (isActive) {         drawCircle(             color = Color.Blue.copy(alpha = 0.3f),             radius = digitSize \/ 1.8f,             center = Offset(x, y)         )     } }<\/code><\/pre>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043e\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.<\/p>\n<details class=\"spoiler\">\n<summary>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/cfd\/dc2\/17b\/cfddc217b203f476d2d1220e62dc720c.gif\" width=\"800\" height=\"1422\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/cfd\/dc2\/17b\/cfddc217b203f476d2d1220e62dc720c.gif\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0432 \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u044f \u0440\u0435\u0448\u0438\u043b \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043e\u0434\u043d\u0443 \u0441\u0442\u043e\u0440\u043e\u043d\u0443, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u0442 0 \u0434\u043e 270 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432<\/p>\n<pre><code class=\"kotlin\">val canRotate = accumulatedDelta in 0f..270f  if (canRotate) {     rotationAngle = targetRotation.coerceIn(0f, 270f)     accumulatedDelta = (accumulatedDelta + deltaAngle).coerceIn(0f, 270f) }<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6cc\/a15\/76d\/6cca1576df5f4783827e90843eacd27b.gif\" width=\"800\" height=\"1422\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6cc\/a15\/76d\/6cca1576df5f4783827e90843eacd27b.gif\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0418 \u0432\u043e\u0442 \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432\u0435\u0441\u044c \u043a\u043e\u0434 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">private class DigitBounds(     var left: Float = 0f,     var top: Float = 0f,     var right: Float = 0f,     var bottom: Float = 0f ) {     fun contains(point: Offset): Boolean {         return point.x &gt;= left &amp;&amp; point.x &lt;= right &amp;&amp;                 point.y &gt;= top &amp;&amp; point.y &lt;= bottom     }      fun update(x: Float, y: Float, size: Float) {         left = x - size \/ 2         top = y - size \/ 2         right = x + size \/ 2         bottom = y + size \/ 2     } }  private val digits = listOf(\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"0\")  @Composable fun OldPhone(     modifier: Modifier, ) {     var rotationAngle by remember { mutableFloatStateOf(0f) }     val textMeasurer = rememberTextMeasurer()     val typography = MaterialTheme.typography.headlineLarge     var accumulatedDelta by remember { mutableFloatStateOf(0f) }     var activeDigitIndex by remember { mutableIntStateOf(-1) }      val digitBoundsList = remember { List(digits.size) { DigitBounds() } }      Canvas(         modifier = modifier             .aspectRatio(1f)             .pointerInput(Unit) {                 awaitEachGesture {                     val down = awaitFirstDown()                      val rotatedPoint = down.position.rotate(                         center = size.center,                         degrees = -rotationAngle,                     )                      val digitHit = digitBoundsList.indexOfFirst { it.contains(rotatedPoint) }                     activeDigitIndex = digitHit                      if (digitHit != -1) {                         var currentAngle = calculateAngle(down.position, size.center)                          do {                             val event = awaitPointerEvent()                             val newAngle = calculateAngle(event.changes.first().position, size.center)                             val deltaAngle = (newAngle - currentAngle).let { delta -&gt;                                 when {                                     delta &gt; 180f -&gt; delta - 360f                                     delta &lt; -180f -&gt; delta + 360f                                     else -&gt; delta                                 }                             }                              val targetRotation = rotationAngle + deltaAngle                              val canRotate = accumulatedDelta in 0f..270f                             if (canRotate) {                                 rotationAngle = targetRotation.coerceIn(0f, 270f)                                 accumulatedDelta = (accumulatedDelta + deltaAngle).coerceIn(0f, 270f)                             }                              currentAngle = newAngle                             event.changes.first().consume()                         } while (event.changes.any { it.pressed })                          activeDigitIndex = -1                     }                 }             }     ) {         val center = Offset(size.width \/ 2, size.height \/ 2)         val radius = size.width \/ 2f - 40.dp.toPx()         val digitSize = 64.dp.toPx()         val angleStep = -270f \/ digits.size          digits.forEachIndexed { index, _ -&gt;             val angleInDegrees = index * angleStep - 45             val angleInRadians = Math.toRadians(angleInDegrees.toDouble())              val x = center.x + radius * cos(angleInRadians).toFloat()             val y = center.y + radius * sin(angleInRadians).toFloat()              digitBoundsList[index].update(x, y, digitSize)         }          rotate(rotationAngle) {             drawCircle(                 color = Color.White,             )              digits.forEachIndexed { index, digit -&gt;                 val bounds = digitBoundsList[index]                 val x = (bounds.left + bounds.right) \/ 2                 val y = (bounds.top + bounds.bottom) \/ 2                  val textLayoutResult: TextLayoutResult = textMeasurer.measure(                     text = digit,                     style = typography,                 )                  drawText(                     textLayoutResult = textLayoutResult,                     topLeft = Offset(                         x - textLayoutResult.size.width \/ 2f,                         y - textLayoutResult.size.height \/ 2f,                     ),                 )                  val isActive = index == activeDigitIndex                  if (isActive) {                     drawCircle(                         color = Color.Blue.copy(alpha = 0.3f),                         radius = digitSize \/ 1.8f,                         center = Offset(x, y)                     )                 }             }         }     } }  private fun calculateAngle(position: Offset, center: IntOffset): Float {     val dx = position.x - center.x     val dy = position.y - center.y     return Math.toDegrees(atan2(dy.toDouble(), dx.toDouble())).toFloat() }  fun Offset.rotate(     center: IntOffset,     degrees: Float, ): Offset {     val angle = Math.toRadians(degrees.toDouble())     val cos = cos(angle).toFloat()     val sin = sin(angle).toFloat()      val x = this.x - center.x     val y = this.y - center.y      return Offset(         x = center.x + (x * cos - y * sin),         y = center.y + (x * sin + y * cos)     ) }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h2>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u0434\u0438\u0441\u043a\u0430 \u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0434\u0438\u0441\u043a\u043e\u0432\u044b\u043c \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u043c, \u0442\u043e \u043f\u043e\u043c\u043d\u0438\u0442\u0435, \u0447\u0442\u043e \u0434\u0438\u0441\u043a \u0432\u0441\u0435\u0433\u0434\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0441\u0442\u043e\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u0431\u0440\u0430\u0442\u044c \u0441 \u043d\u0435\u0433\u043e \u043f\u0430\u043b\u0435\u0446. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u043c \u044d\u0442\u0443 \u043c\u0435\u0445\u0430\u043d\u0438\u043a\u0443<\/p>\n<p>\u0417\u0430\u0432\u0435\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0430\u043d\u0438\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0434\u0438\u0441\u043a\u0430, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u0444\u0443\u043d\u043a\u0446\u0438\u044e animateFloatAsState<\/p>\n<pre><code class=\"kotlin\">    val animatedRotationAngle by animateFloatAsState(         targetValue = rotationAngle     )<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f animatedRotationAngle \u0432\u043c\u0435\u0441\u0442\u043e rotationAngle \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 rotate<\/p>\n<pre><code class=\"kotlin\">Canvas {   \/\/ \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043a\u043e\u0434      rotate(animatedRotationAngle) {     \/\/ \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043a\u043e\u0434   } }<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c \u0443\u0433\u043e\u043b \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430 \u0432 0, \u043a\u043e\u0433\u0434\u0430 \u0436\u0435\u0441\u0442 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f<\/p>\n<pre><code class=\"kotlin\">Canvas(         modifier = modifier             .aspectRatio(1f)             .pointerInput(Unit) {                 awaitEachGesture {                         \/\/ \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043a\u043e\u0434                          rotationAngle = 0f                         activeDigitIndex = -1                     }                 }             }     ) {         \/\/ \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043a\u043e\u0434     }<\/code><\/pre>\n<p>\u041a\u0441\u0442\u0430\u0442\u0438, \u043f\u043e\u043a\u0430 \u043f\u0438\u0441\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u044e, \u0437\u0430\u043c\u0435\u0442\u0438\u043b, \u0447\u0442\u043e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f accumulatedDelta \u043d\u0435 \u043d\u0443\u0436\u043d\u0430, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u0435\u0435 \u0443\u0434\u0430\u043b\u0438\u043b<\/p>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432\u0435\u0441\u044c \u043a\u043e\u0434 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u043d\u0430 \u0434\u0430\u043d\u043d\u043e\u043c \u044d\u0442\u0430\u043f\u0435:<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">private class DigitBounds(     var left: Float = 0f,     var top: Float = 0f,     var right: Float = 0f,     var bottom: Float = 0f ) {     fun contains(point: Offset): Boolean {         return point.x &gt;= left &amp;&amp; point.x &lt;= right &amp;&amp;                 point.y &gt;= top &amp;&amp; point.y &lt;= bottom     }      fun update(x: Float, y: Float, size: Float) {         left = x - size \/ 2         top = y - size \/ 2         right = x + size \/ 2         bottom = y + size \/ 2     } }  private val digits = listOf(\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"0\")  @Composable fun OldPhone(     modifier: Modifier, ) {     var rotationAngle by remember { mutableFloatStateOf(0f) }     val textMeasurer = rememberTextMeasurer()     val typography = MaterialTheme.typography.headlineLarge     var activeDigitIndex by remember { mutableIntStateOf(-1) }      val digitBoundsList = remember { List(digits.size) { DigitBounds() } }      val animatedRotationAngle by animateFloatAsState(         targetValue = rotationAngle     )      Canvas(         modifier = modifier             .aspectRatio(1f)             .pointerInput(Unit) {                 awaitEachGesture {                     val down = awaitFirstDown()                      val rotatedPoint = down.position.rotate(                         center = size.center,                         degrees = -rotationAngle,                     )                      val digitHit = digitBoundsList.indexOfFirst { it.contains(rotatedPoint) }                     activeDigitIndex = digitHit                      if (digitHit != -1) {                         var currentAngle = calculateAngle(down.position, size.center)                          do {                             val event = awaitPointerEvent()                             val newAngle = calculateAngle(event.changes.first().position, size.center)                             val deltaAngle = (newAngle - currentAngle).let { delta -&gt;                                 when {                                     delta &gt; 180f -&gt; delta - 360f                                     delta &lt; -180f -&gt; delta + 360f                                     else -&gt; delta                                 }                             }                              val targetRotation = rotationAngle + deltaAngle                              if (rotationAngle in 0f..270f) {                                 rotationAngle = targetRotation.coerceIn(0f, 270f)                             }                              currentAngle = newAngle                             event.changes.first().consume()                         } while (event.changes.any { it.pressed })                          rotationAngle = 0f                         activeDigitIndex = -1                     }                 }             }     ) {         val center = Offset(size.width \/ 2, size.height \/ 2)         val radius = size.width \/ 2f - 40.dp.toPx()         val digitSize = 64.dp.toPx()         val angleStep = -270f \/ digits.size          digits.forEachIndexed { index, _ -&gt;             val angleInDegrees = index * angleStep - 45             val angleInRadians = Math.toRadians(angleInDegrees.toDouble())              val x = center.x + radius * cos(angleInRadians).toFloat()             val y = center.y + radius * sin(angleInRadians).toFloat()              digitBoundsList[index].update(x, y, digitSize)         }          rotate(animatedRotationAngle) {             drawCircle(                 color = Color.White,             )              digits.forEachIndexed { index, digit -&gt;                 val bounds = digitBoundsList[index]                 val x = (bounds.left + bounds.right) \/ 2                 val y = (bounds.top + bounds.bottom) \/ 2                  val textLayoutResult: TextLayoutResult = textMeasurer.measure(                     text = digit,                     style = typography,                 )                  drawText(                     textLayoutResult = textLayoutResult,                     topLeft = Offset(                         x - textLayoutResult.size.width \/ 2f,                         y - textLayoutResult.size.height \/ 2f,                     ),                 )                  val isActive = index == activeDigitIndex                  if (isActive) {                     drawCircle(                         color = Color.Blue.copy(alpha = 0.3f),                         radius = digitSize \/ 1.8f,                         center = Offset(x, y)                     )                 }             }         }     } }  private fun calculateAngle(position: Offset, center: IntOffset): Float {     val dx = position.x - center.x     val dy = position.y - center.y     return Math.toDegrees(atan2(dy.toDouble(), dx.toDouble())).toFloat() }  fun Offset.rotate(     center: IntOffset,     degrees: Float, ): Offset {     val angle = Math.toRadians(degrees.toDouble())     val cos = cos(angle).toFloat()     val sin = sin(angle).toFloat()      val x = this.x - center.x     val y = this.y - center.y      return Offset(         x = center.x + (x * cos - y * sin),         y = center.y + (x * sin + y * cos)     ) }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/p>\n<details class=\"spoiler\">\n<summary>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e13\/d20\/df5\/e13d20df544887a896bb5d98ce3f3799.gif\" width=\"800\" height=\"1422\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e13\/d20\/df5\/e13d20df544887a896bb5d98ce3f3799.gif\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041d\u0430\u0448 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u044b\u0439 \u0434\u0438\u0441\u043a \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0432\u0441\u0435 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0445\u043e\u0436\u0438\u043c \u043d\u0430 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439!<\/p>\n<h2>\u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0448\u0442\u0440\u0438\u0445\u0438<\/h2>\n<p>\u0421\u0435\u0439\u0447\u0430\u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 \u0443\u0433\u043e\u043b \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0446\u0438\u0444\u0440\u044b \u0438 \u0432\u0441\u0435\u0433\u0434\u0430 \u0440\u0430\u0432\u0435\u043d 270 \u0433\u0440\u0430\u0434\u0443\u0441\u0430\u043c, \u043d\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0430 \u0434\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430\u0445 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u043b\u0438 \u0443\u043f\u043e\u0440, \u0434\u0430\u043b\u044c\u0448\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0434\u0438\u0441\u043a \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f. \u0414\u0440\u0443\u0433\u0438\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438, \u043a\u0430\u0436\u0434\u0430\u044f \u0446\u0438\u0444\u0440\u0430 \u0438\u043c\u0435\u043b\u0430 \u0440\u0430\u0437\u043d\u0443\u044e \u0434\u043b\u0438\u043d\u0443 \u0445\u043e\u0434\u0430: 1 &#8212; c\u0430\u043c\u0443\u044e \u043a\u043e\u0440\u043e\u0442\u043a\u0443\u044e, \u0430 0 &#8212; \u0441\u0430\u043c\u0443\u044e \u0434\u043b\u0438\u043d\u043d\u0443\u044e<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0438 \u044d\u0442\u043e<\/p>\n<pre><code class=\"kotlin\">\/\/ \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0434\u043b\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0446\u0438\u0444\u0440\u044b val maxRotation = (digitHit + 1) * 27f \/\/ 270 \/ 10 = 27 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432 \u043d\u0430 \u0446\u0438\u0444\u0440\u0443  if (rotationAngle &lt;= maxRotation) {     rotationAngle = targetRotation.coerceIn(0f, maxRotation) }<\/code><\/pre>\n<p>\u0414\u0430, \u0432\u043e\u0442 \u0442\u0430\u043a \u043f\u0440\u043e\u0441\u0442\u043e<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u0432\u0435\u0449\u0435\u0439, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u0431\u043e\u0434 \u0432\u043e\u043a\u0440\u0443\u0433 \u0434\u0438\u0441\u043a\u0430 \u0438 \u043c\u0430\u043b\u044b\u0439 \u043a\u0440\u0443\u0433 \u0441 \u0442\u043e\u0447\u043a\u0430\u043c\u0438 \u0432\u043d\u0443\u0442\u0440\u0438. \u0412\u0441\u0435 \u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u0440\u0438\u0434\u0430\u0442\u044c \u0434\u0438\u043d\u0430\u043c\u0438\u043a\u0443 \u0438 \u0436\u0438\u0432\u043e\u0441\u0442\u044c \u043d\u0430\u0448\u0435\u043c\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443!<\/p>\n<pre><code class=\"kotlin\">\/\/ \u0440\u0438\u0441\u0443\u0435\u043c \u0432\u043d\u0435\u0448\u043d\u044e\u044e \u0440\u0430\u043c\u043a\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u043e\u0433\u043e \u0434\u0438\u0441\u043a\u0430 drawCircle(     color = Color.LightGray,     radius = size.width \/ 2f,     center = center,     style = Stroke(         width = 8.dp.toPx(),     ), )  \/\/ \u0440\u0430\u0434\u0438\u0443\u0441 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u0434\u0438\u0441\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0446\u0435\u043d\u0442\u0440\u0435 val centerCircleRadius = size.width \/ 3.21f  \/\/ \u0440\u0438\u0441\u0443\u0435\u043c \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0434\u0438\u0441\u043a \u0441\u0435\u0440\u043e\u0433\u043e \u0446\u0432\u0435\u0442\u0430 drawCircle(     color = Color.LightGray,     radius = centerCircleRadius,     center = center )  \/\/ \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u044b \u043f\u043e \u043a\u0440\u0443\u0433\u0443 val dotsCount = 12  \/\/ \u0440\u0430\u0434\u0438\u0443\u0441 \u043a\u0430\u0436\u0434\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 val dotRadius = 2.dp.toPx()  \/\/ \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u0434\u0438\u0441\u043a\u0430 \u0434\u043e \u0442\u043e\u0447\u0435\u043a (\u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043c\u0435\u043d\u044c\u0448\u0435 \u0440\u0430\u0434\u0438\u0443\u0441\u0430 \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u0438\u0441\u043a\u0430) val dotDistance = centerCircleRadius - 10.dp.toPx()  \/\/ \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0442\u043e\u0447\u043a\u0438 \u043f\u043e \u043a\u0440\u0443\u0433\u0443 repeat(dotsCount) { index -&gt;          \/\/ \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u0443\u0433\u043e\u043b \u0434\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0442\u043e\u0447\u043a\u0438 (360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432 \u0434\u0435\u043b\u0438\u043c \u043d\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a)     val angle = (360f \/ dotsCount) * index          \/\/ \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u043c \u0443\u0433\u043e\u043b \u0432 \u0440\u0430\u0434\u0438\u0430\u043d\u044b \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u0442\u0440\u0438\u0433\u043e\u043d\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u0445     val angleRad = Math.toRadians(angle.toDouble())          \/\/ \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c x \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0443 \u0442\u043e\u0447\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043a\u043e\u0441\u0438\u043d\u0443\u0441 \u0443\u0433\u043b\u0430     val dotX = center.x + dotDistance * cos(angleRad).toFloat()          \/\/ \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c y \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0443 \u0442\u043e\u0447\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u0438\u043d\u0443\u0441 \u0443\u0433\u043b\u0430     val dotY = center.y + dotDistance * sin(angleRad).toFloat()      \/\/ \u0440\u0438\u0441\u0443\u0435\u043c \u0442\u043e\u0447\u043a\u0443     drawCircle(         color = Color.Gray,         radius = dotRadius,         center = Offset(dotX, dotY)     ) }<\/code><\/pre>\n<p>\u0418 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u0434\u0435\u0442\u0430\u043b\u044c, \u043c\u043e\u0436\u043d\u043e \u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u0447\u0442\u043e \u044d\u0442\u043e \u043d\u0430\u0448\u0430 \u0432\u0438\u0448\u0435\u043d\u043a\u0430 \u043d\u0430 \u0442\u043e\u0440\u0442\u0435 &#8212; \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0442\u0435\u043b\u044c \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043f\u0430\u043b\u044c\u0446\u0430 \u0438\u043b\u0438, \u0435\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u0442\u0435, \u0441\u0442\u043e\u043f\u043f\u0435\u0440.<\/p>\n<pre><code class=\"kotlin\">\/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0442\u043e\u043f\u043f\u0435\u0440 \u0432 \u0432\u0438\u0434\u0435 \u0442\u0440\u0430\u043f\u0435\u0446\u0438\u0438 val stopperAngle = 0.0 val stopperOuterDistance = size.width \/ 2f \/\/ \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0440\u0430\u0434\u0438\u0443\u0441, \u043d\u0430 \u0433\u0440\u0430\u043d\u0438\u0446\u0435 \u0434\u0438\u0441\u043a\u0430 val stopperInnerDistance = size.width \/ 2f - 48.dp.toPx() \/\/ \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u043b\u0438 \u0433\u043b\u0443\u0431\u0438\u043d\u0443 \u0441\u0442\u043e\u043f\u043f\u0435\u0440\u0430  \/\/ \u0422\u043e\u0447\u043a\u0438 \u0434\u043b\u044f \u0432\u043d\u0435\u0448\u043d\u0435\u0439 (\u0448\u0438\u0440\u043e\u043a\u043e\u0439) \u0447\u0430\u0441\u0442\u0438 \u0442\u0440\u0430\u043f\u0435\u0446\u0438\u0438 val outerLeftX = center.x + stopperOuterDistance * cos(Math.toRadians(stopperAngle - 5).toDouble()).toFloat() val outerLeftY = center.y + stopperOuterDistance * sin(Math.toRadians(stopperAngle - 5).toDouble()).toFloat() val outerRightX = center.x + stopperOuterDistance * cos(Math.toRadians(stopperAngle + 5).toDouble()).toFloat() val outerRightY = center.y + stopperOuterDistance * sin(Math.toRadians(stopperAngle + 5).toDouble()).toFloat()  \/\/ \u0422\u043e\u0447\u043a\u0438 \u0434\u043b\u044f \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 (\u0443\u0437\u043a\u043e\u0439) \u0447\u0430\u0441\u0442\u0438 \u0442\u0440\u0430\u043f\u0435\u0446\u0438\u0438 val innerLeftX = center.x + stopperInnerDistance * cos(Math.toRadians(stopperAngle - 2.5).toDouble()).toFloat() val innerLeftY = center.y + stopperInnerDistance * sin(Math.toRadians(stopperAngle - 2.5).toDouble()).toFloat() val innerRightX = center.x + stopperInnerDistance * cos(Math.toRadians(stopperAngle + 2.5).toDouble()).toFloat() val innerRightY = center.y + stopperInnerDistance * sin(Math.toRadians(stopperAngle + 2.5).toDouble()).toFloat()  drawPath(     path = Path().apply {         moveTo(outerLeftX, outerLeftY)         lineTo(outerRightX, outerRightY)         lineTo(innerRightX, innerRightY)         lineTo(innerLeftX, innerLeftY)         close()     },     color = Color.LightGray )<\/code><\/pre>\n<p>\u041c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u0432\u0441\u0435, \u0447\u0442\u043e \u0445\u043e\u0442\u0435\u043b\u0438. \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u0430\u043c\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u0441\u043b\u0430\u0434\u0438\u0442\u044c\u0441\u044f \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c!<\/p>\n<details class=\"spoiler\">\n<summary>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/abc\/e85\/c16\/abce85c1649aa58c0b3a623d54c9427f.gif\" width=\"800\" height=\"1422\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/abc\/e85\/c16\/abce85c1649aa58c0b3a623d54c9427f.gif\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<h2>\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435<\/h2>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0441\u0442\u0430\u0442\u044c\u0438, \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445 \u0431\u044b\u043b\u043e \u0441\u043f\u0440\u0430\u0432\u0435\u0434\u043b\u0438\u0432\u043e \u043e\u0442\u043c\u0435\u0447\u0435\u043d\u043e, \u0447\u0442\u043e \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0434\u0438\u0441\u043a\u043e\u0432\u043e\u043c \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0435 \u0446\u0438\u0444\u0440\u044b \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u043d\u0435\u043f\u043e\u0434\u0432\u0438\u0436\u043d\u044b\u043c\u0438, \u0430 \u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0434\u0438\u0441\u043a \u0441 \u043e\u0442\u0432\u0435\u0440\u0441\u0442\u0438\u044f\u043c\u0438 \u043d\u0430\u0434 \u043d\u0438\u043c\u0438. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0432\u0438\u0434 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043b \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u0435\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u0438. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f<\/p>\n<p>\u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0430 \u0432\u0440\u0430\u0449\u0430\u044e\u0449\u0435\u0439\u0441\u044f \u043a\u0440\u044b\u0448\u043a\u0438 \u0441 \u043e\u0442\u0432\u0435\u0440\u0441\u0442\u0438\u044f\u043c\u0438 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c Path \u0441 \u0440\u0435\u0436\u0438\u043c\u043e\u043c \u0437\u0430\u043b\u0438\u0432\u043a\u0438 EvenOdd:<\/p>\n<pre><code class=\"kotlin\">val diskPath = Path().apply {     \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u0440\u0443\u0433     addOval(         Rect(             center = center,             radius = size.width \/ 2f         )     )      \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043e\u0442\u0432\u0435\u0440\u0441\u0442\u0438\u044f \u0434\u043b\u044f \u0446\u0438\u0444\u0440     digits.forEachIndexed { index, _ -&gt;         val angleInDegrees = index * angleStep - 45         val angleInRadians = Math.toRadians(angleInDegrees.toDouble())          val x = center.x + radius * cos(angleInRadians).toFloat()         val y = center.y + radius * sin(angleInRadians).toFloat()          addOval(             Rect(                 center = Offset(x, y),                 radius = digitSize \/ 2f             )         )     }      \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043e\u0442\u0432\u0435\u0440\u0441\u0442\u0438\u0435 \u0434\u043b\u044f \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u0438     addOval(         Rect(             center = center,             radius = centerCircleRadius         )     )      \/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c EvenOdd \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043e\u0442\u0432\u0435\u0440\u0441\u0442\u0438\u0439     fillType = PathFillType.EvenOdd }<\/code><\/pre>\n<p>\u0414\u043b\u044f \u0434\u043e\u0441\u0442\u0438\u0436\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u0440\u0435\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e \u0432\u0438\u0434\u0430 \u0431\u044b\u043b\u0438 \u043f\u043e\u0434\u043e\u0431\u0440\u0430\u043d\u044b \u0446\u0432\u0435\u0442\u0430, \u0438\u043c\u0438\u0442\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0445 \u0434\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432:<\/p>\n<pre><code class=\"kotlin\">val baseColor = Color(0xFFF5F2EA) \/\/ \u0442\u0435\u043f\u043b\u044b\u0439 \u043a\u0440\u0435\u043c\u043e\u0432\u044b\u0439 \u0434\u043b\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0434\u0438\u0441\u043a\u0430 val coverColor = Color(0xFF2B2522).copy(alpha = 0.92f) \/\/ \u0431\u0430\u043a\u0435\u043b\u0438\u0442\u043e\u0432\u044b\u0439 val centerAndFrameColor = Color(0xFFE5E0D5) \/\/ \u0441\u0432\u0435\u0442\u043b\u044b\u0439 \u0431\u0435\u0436\u0435\u0432\u044b\u0439 \u0434\u043b\u044f \u0434\u0435\u0442\u0430\u043b\u0435\u0439<\/code><\/pre>\n<p>\u041f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u043c \u0446\u0432\u0435\u0442\u0430 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043c\u0435\u0441\u0442\u0430\u0445:<\/p>\n<pre><code class=\"kotlin\">\/\/ \u041e\u0441\u043d\u043e\u0432\u0430 \u0441 \u0446\u0438\u0444\u0440\u0430\u043c\u0438 drawCircle(     color = baseColor, )  \/\/ \u0412\u0440\u0430\u0449\u0430\u044e\u0449\u0430\u044f\u0441\u044f \u043a\u0440\u044b\u0448\u043a\u0430 \u0441 \u043e\u0442\u0432\u0435\u0440\u0441\u0442\u0438\u044f\u043c\u0438 drawPath(     path = diskPath,     color = coverColor, )  \/\/ \u0426\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u0440\u0443\u0433 \u0438 \u0440\u0430\u043c\u043a\u0430 drawCircle(     color = centerAndFrameColor,     radius = centerCircleRadius,     center = center )<\/code><\/pre>\n<h2>\u0411\u043e\u043d\u0443\u0441<\/h2>\n<p>\u0420\u0430\u0437 \u0443\u0436 \u043c\u044b \u0434\u0435\u043b\u0430\u043b\u0438 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u044b\u0439 \u0434\u0438\u0441\u043a, \u0442\u043e \u043e\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0443\u043c\u0435\u0442\u044c \u043d\u0430\u0431\u0438\u0440\u0430\u0442\u044c \u043d\u043e\u043c\u0435\u0440\u0430, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u044d\u0442\u043e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b<\/p>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0435\u043c \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 \u043d\u0430\u0448\u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438:<\/p>\n<pre><code class=\"kotlin\">@Composable fun OldPhone(     modifier: Modifier,     onDigitSelected: (String) -&gt; Unit, \/\/ \u043a\u043e\u043b\u043b\u0431\u0435\u043a \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043d\u0430\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0446\u0438\u0444\u0440\u044b ) {     \/\/ \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434 }<\/code><\/pre>\n<p>\u0426\u0438\u0444\u0440\u0430 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043d\u0430\u0431\u0440\u0430\u043d\u043d\u043e\u0439, \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043e\u0432\u0435\u043b \u0434\u0438\u0441\u043a \u0434\u043e \u043a\u043e\u043d\u0446\u0430, \u0442\u043e \u0435\u0441\u0442\u044c \u0434\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0434\u043b\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0446\u0438\u0444\u0440\u044b. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u044d\u0442\u0443 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0436\u0435\u0441\u0442\u043e\u0432:<\/p>\n<pre><code class=\"kotlin\">do {     val event = awaitPointerEvent()     val newAngle = calculateAngle(event.changes.first().position, size.center)     val deltaAngle = (newAngle - currentAngle).let { delta -&gt;         when {             delta &gt; 180f -&gt; delta - 360f             delta &lt; -180f -&gt; delta + 360f             else -&gt; delta         }     }      val targetRotation = rotationAngle + deltaAngle     val maxRotation = (digitHit + 1) * 27f      if (rotationAngle &lt;= maxRotation) {         rotationAngle = targetRotation.coerceIn(0f, maxRotation)     }      currentAngle = newAngle     event.changes.first().consume() } while (event.changes.any { it.pressed })  \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0434\u043e\u0432\u0435\u043b \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u0438\u0441\u043a \u0434\u043e \u043a\u043e\u043d\u0446\u0430 val isDigitFullySelected = rotationAngle &gt;= maxRotation * 0.9f \/\/ \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0437\u0430\u043f\u0430\u0441 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 if (isDigitFullySelected) {     onDigitSelected(digits[digitHit]) \/\/ \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u043b\u043b\u0431\u0435\u043a \u0441 \u043d\u0430\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0446\u0438\u0444\u0440\u043e\u0439 }  rotationAngle = 0f activeDigitIndex = -1<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0432\u0435\u0440\u0441\u0442\u043a\u0443 \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">Box(     modifier = Modifier         .fillMaxSize()         .padding(innerPadding),     contentAlignment = Alignment.Center, ) {     Column(         horizontalAlignment = Alignment.CenterHorizontally,         verticalArrangement = Arrangement.spacedBy(8.dp),     ) {         var phoneNumber by remember {             mutableStateOf(\"\")         }          Text(             modifier = Modifier                 .align(Alignment.Start)                 .padding(                     horizontal = 16.dp,                 ),             text = \"Phone number: $phoneNumber\",             style = MaterialTheme.typography.headlineLarge,         )          OldPhone(             modifier = Modifier.padding(innerPadding),             onDigitSelected = { digit -&gt;                 phoneNumber += digit             },         )          Button(             onClick = {                 phoneNumber = \"\"             }         ) {             Text(                 text = \"Clear phone number\",             )         }     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0441\u043b\u0435 \u0432\u0441\u0435\u0445 \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044b\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439<\/p>\n<details class=\"spoiler\">\n<summary>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/403\/596\/da6\/403596da6ebb3a63cfcd62e784302965.gif\" width=\"800\" height=\"1422\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/403\/596\/da6\/403596da6ebb3a63cfcd62e784302965.gif\"\/><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u043e\u043a \u043d\u0430\u0448 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u0442\u0430\u043b \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u0431\u043b\u0438\u0436\u0435 \u043a \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0434\u0438\u0441\u043a\u043e\u0432\u043e\u043c\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443. \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f:<\/p>\n<ol>\n<li>\n<p>\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0430 \u043c\u0435\u0445\u0430\u043d\u0438\u043a\u0430 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f &#8212; \u0442\u0435\u043f\u0435\u0440\u044c \u0446\u0438\u0444\u0440\u044b \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u043d\u0435\u043f\u043e\u0434\u0432\u0438\u0436\u043d\u044b\u043c\u0438, \u0430 \u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0434\u0438\u0441\u043a \u0441 \u043e\u0442\u0432\u0435\u0440\u0441\u0442\u0438\u044f\u043c\u0438 \u043d\u0430\u0434 \u043d\u0438\u043c\u0438, \u043a\u0430\u043a \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u043c \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0435<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0434\u043e\u0431\u0440\u0430\u043d\u044b \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0447\u043d\u044b\u0435 \u0446\u0432\u0435\u0442\u0430, \u0438\u043c\u0438\u0442\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u0434\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432 &#8212; \u0431\u0430\u043a\u0435\u043b\u0438\u0442 \u0438 \u043f\u043b\u0430\u0441\u0442\u0438\u043a<\/p>\n<\/li>\n<li>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043d\u0430\u0431\u0440\u0430\u043d\u043d\u044b\u0435 \u0446\u0438\u0444\u0440\u044b \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043b\u043b\u0431\u0435\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u043c \u043d\u0430\u0431\u043e\u0440\u0435 \u043d\u043e\u043c\u0435\u0440\u0430<\/p>\n<\/li>\n<\/ol>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u0442\u0430\u043b \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0431\u043e\u043b\u0435\u0435 \u0440\u0435\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u043c \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e, \u043d\u043e \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c &#8212; \u0442\u0435\u043f\u0435\u0440\u044c \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0434\u043b\u044f \u0432\u0432\u043e\u0434\u0430 \u0446\u0438\u0444\u0440!<\/p>\n<h3>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a, \u0448\u0430\u0433 \u0437\u0430 \u0448\u0430\u0433\u043e\u043c, \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439 \u043a\u0443\u0441\u043e\u0447\u0435\u043a \u043b\u0430\u043c\u043f\u043e\u0432\u043e\u0433\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u043e\u0432\u043e\u0433\u043e \u043c\u0438\u0440\u0430 \u0432 \u0431\u0435\u0437\u0434\u0443\u0448\u043d\u043e\u043c \u0446\u0438\u0444\u0440\u043e\u0432\u043e\u043c \u043c\u0438\u0440\u0435. \u041b\u0438\u0447\u043d\u043e \u044f \u043e\u0447\u0435\u043d\u044c \u0434\u043e\u0432\u043e\u043b\u0435\u043d \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c. \u041f\u043e\u043b\u043d\u044b\u0439 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0443 \u043c\u0435\u043d\u044f \u0432 <a href=\"https:\/\/github.com\/arttttt\/OldPhoneAnimation\" rel=\"noopener noreferrer nofollow\">GitHub<\/a>.<\/p>\n<p>\u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0432\u0430\u043c \u043f\u043e\u043d\u0440\u0430\u0432\u0438\u043b\u0430\u0441\u044c.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0434\u043e\u0447\u0438\u0442\u0430\u043b\u0438 \u0434\u043e \u043a\u043e\u043d\u0446\u0430, \u0442\u043e \u0445\u043e\u0447\u0443 \u043f\u0440\u0438\u0433\u043b\u0430\u0441\u0438\u0442\u044c \u0432\u0430\u0441 \u043d\u0430 \u0441\u0432\u043e\u0438 <a href=\"https:\/\/www.youtube.com\/@Android_Insights\" rel=\"noopener noreferrer nofollow\">YouTube<\/a> \u0438 <a href=\"https:\/\/t.me\/android_insights\" rel=\"noopener noreferrer nofollow\">Telegram<\/a> \u043a\u0430\u043d\u0430\u043b\u044b.<\/p>\n<p>\u0421\u043f\u0430\u0441\u0438\u0431\u043e!<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/871956\/\"> https:\/\/habr.com\/ru\/articles\/871956\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u0410\u0440\u0442\u0435\u043c \u0438 \u044f \u0430\u0432\u0442\u043e\u0440 \u0438 \u0432\u0435\u0434\u0443\u0449\u0438\u0439 <a href=\"https:\/\/www.youtube.com\/@Android_Insights\" rel=\"noopener noreferrer nofollow\">YouTube<\/a> \u0438 <a href=\"https:\/\/t.me\/android_insights\" rel=\"noopener noreferrer nofollow\">Telegram<\/a> \u043a\u0430\u043d\u0430\u043b\u043e\u0432\u00a0Android Insights.<\/p>\n<p>\u0414\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f &#8212; \u0438\u0434\u0435\u0439\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0435\u0439 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/867822\/\" rel=\"noopener noreferrer nofollow\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 Custom Layout \u0432 Jetpack Compose<\/a><\/p>\n<h2>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0423\u0436\u0435 \u043e\u0442\u0433\u0440\u0435\u043c\u0435\u043b\u0438 \u043f\u0440\u0430\u0437\u0434\u043d\u0438\u0447\u043d\u044b\u0435 \u0441\u0430\u043b\u044e\u0442\u044b, \u0437\u0430\u043f\u0430\u0441\u044b \u0441\u0430\u043b\u0430\u0442\u043e\u0432 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0442 \u043a \u043a\u043e\u043d\u0446\u0443, \u044f \u0442\u0438\u0445\u043e \u0441\u0438\u0434\u0435\u043b \u0434\u043e\u043c\u0430, \u043f\u0440\u0435\u0434\u0430\u0432\u0430\u044f\u0441\u044c \u043d\u043e\u0441\u0442\u0430\u043b\u044c\u0433\u0438\u0438 \u043e \u0431\u044b\u043b\u044b\u0445 \u0432\u0440\u0435\u043c\u0435\u043d\u0430\u0445. \u041d\u043e \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0442\u0435\u0441\u0442. \u0417\u043d\u0430\u0435\u0442\u0435 \u043b\u0438 \u0432\u044b, \u0447\u0442\u043e \u0437\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0438 \u043d\u0438\u0436\u0435?<\/p>\n<details class=\"spoiler\">\n<summary>\u041e\u043b\u0434\u044b \u043d\u0430 \u043c\u0435\u0441\u0442\u0435?<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u0430, \u043f\u043e \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043f\u0440\u0438\u0447\u0438\u043d\u0435 \u043c\u043d\u0435 \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043b\u0441\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0435\u043b\u0435\u0444\u043e\u043d, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u044f \u043c\u043e\u0433 \u0434\u043e\u043b\u0433\u043e \u0440\u0430\u0437\u0433\u043e\u0432\u0430\u0440\u0438\u0432\u0430\u0442\u044c \u0441 \u0434\u0440\u0443\u0437\u044c\u044f\u043c\u0438 \u0432 \u0434\u0435\u0442\u0441\u0442\u0432\u0435. <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0431\u043b\u0438\u0436\u0435 \u043a \u0434\u0435\u043b\u0443, \u0443 \u0442\u0430\u043a\u0438\u0445 \u0441\u0442\u0430\u0440\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432 \u043e\u0447\u0435\u043d\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430\u044f \u043c\u0435\u0445\u0430\u043d\u0438\u043a\u0430 \u043d\u0430\u0431\u043e\u0440\u0430 \u043d\u043e\u043c\u0435\u0440\u0430, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e &#8212; \u043d\u0443\u0436\u043d\u043e \u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0434\u0438\u0441\u043a. \u0418 \u043c\u043d\u0435 \u0432 \u0433\u043e\u043b\u043e\u0432\u0443 \u043f\u0440\u0438\u0448\u043b\u0430 \u0438\u0434\u0435\u044f \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0435\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c.<\/p>\n<h2>\u0411\u0430\u0437\u043e\u0432\u044b\u0439 UI<\/h2>\n<p>\u0415\u0441\u043b\u0438 \u0432 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u043b \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 Layout, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u0435\u0431\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043a \u043d\u0438\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u0430, \u0442\u043e \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u044f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <strong>Canvas<\/strong>, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u0435, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0436\u0435\u0441\u0442\u043e\u0432.<\/p>\n<p>\u041b\u044e\u0431\u043e\u0435 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432\u043e\u043a\u0440\u0443\u0433 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043e\u0441\u0438, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u043e\u0441\u044c \u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430. \u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u0445\u0435\u043c\u0430 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c, \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c\u043e \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 \u0434\u0438\u0441\u043a \u043d\u0430\u0431\u043e\u0440\u0430 \u043d\u043e\u043c\u0435\u0440\u0430<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0440\u0430\u0432\u043d\u043e\u043c\u0435\u0440\u043d\u043e \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u0446\u0438\u0444\u0440\u044b \u043d\u0430 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438, \u044f \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e \u0435\u0451 \u043a\u0430\u043a \u043a\u0440\u0443\u0433 \u0441 \u0446\u0435\u043d\u0442\u0440\u043e\u043c. \u0414\u043b\u044f \u0440\u0430\u0432\u043d\u043e\u043c\u0435\u0440\u043d\u043e\u0433\u043e \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0446\u0438\u0444\u0440 \u043f\u043e \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u043a\u0440\u0443\u0433\u0430 \u043d\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0446\u0438\u0444\u0440.<\/p>\n<p>\u0423 \u043d\u0430\u0441 10 \u0446\u0438\u0444\u0440, \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u043a\u0440\u0443\u0433\u0430 \u043a\u0430\u0436\u0434\u0430\u044f \u0437\u0430\u0439\u043c\u0451\u0442 \u0443\u0433\u043e\u043b(<em>\u043f\u043e\u043b\u043d\u044b\u0439 \u043a\u0440\u0443\u0433 &#8212; 360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432<\/em>)<\/p>\n<p>\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0432 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0434\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432, \u044f \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b, \u0447\u0442\u043e \u0443 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u0446\u0438\u0444\u0435\u0440\u0431\u043b\u0430\u0442 \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 3 \/ 4 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430 \u043d\u0430 \u043a\u0440\u0443\u0433\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0448\u0430\u0433 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0446\u0438\u0444\u0440 \u0431\u0443\u0434\u0435\u0442 \u0447\u0443\u0442\u044c \u043c\u0435\u043d\u044c\u0448\u0435<\/p>\n<p>\u041f\u043b\u044e\u0441 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0435 \u0437\u0430\u0431\u044b\u0442\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0448\u0430\u0433 \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u043b\u0438\u0441\u044c \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438<\/p>\n<p>\u0426\u0438\u0444\u0440\u044b \u0440\u0430\u0437\u043c\u0435\u0449\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0443\u0433\u043e\u043b \u0438 \u0440\u0430\u0434\u0438\u0443\u0441. \u042d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0442\u0440\u0438\u0433\u043e\u043d\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439:<\/p>\n<p>\u0417\u0434\u0435\u0441\u044c:<\/p>\n<ul>\n<li>\n<p>centerX, centerY \u2014 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0446\u0435\u043d\u0442\u0440\u0430 \u043a\u0440\u0443\u0433\u0430<\/p>\n<\/li>\n<li>\n<p>radius \u2014 \u0440\u0430\u0434\u0438\u0443\u0441 \u043a\u0440\u0443\u0433\u0430<\/p>\n<\/li>\n<li>\n<p>angle \u2014 \u0443\u0433\u043e\u043b \u0432 \u0440\u0430\u0434\u0438\u0430\u043d\u0430\u0445<\/p>\n<\/li>\n<\/ul>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u0441\u0438\u043d\u0443\u0441\u0430 \u0438 \u0441\u0438\u043d\u0443\u0441\u0430 \u0432 \u041a\u043e\u0442\u043b\u0438\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0441 \u0440\u0430\u0434\u0438\u0430\u043d\u0430\u043c\u0438, \u0430 \u043d\u0435 \u0441 \u0433\u0440\u0430\u0434\u0443\u0441\u0430\u043c\u0438. \u0427\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0443\u0433\u043e\u043b \u0438\u0437 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432 \u0432 \u0440\u0430\u0434\u0438\u0430\u043d\u044b, \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0444\u043e\u0440\u043c\u0443\u043b\u0443:<\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0446\u0438\u0444\u0435\u0440\u0431\u043b\u0430\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b \u0442\u0430\u043a, \u043a\u0430\u043a \u044f \u0445\u043e\u0442\u0435\u043b, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043d\u0430 -45 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u0424\u043e\u0440\u043c\u0443\u043b\u0430 \u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0446\u0438\u0444\u0440\u044b \u0432 \u043e\u0431\u0449\u0435\u043c \u0432\u0438\u0434\u0435:<\/p>\n<p>\u0410 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0442\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0432\u0438\u0434:<\/p>\n<p>\u0415\u0449\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0435 \u0437\u0430\u0431\u044b\u0442\u044c \u043e\u0442\u0446\u0435\u043d\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442, \u043d\u043e \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0441\u043e\u0432\u0441\u0435\u043c \u043b\u0435\u0433\u043a\u043e, \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0444\u043e\u0440\u043c\u0443\u043b\u044b:<\/p>\n<p>width, height \u2014 \u0440\u0430\u0437\u043c\u0435\u0440\u044b \u0442\u0435\u043a\u0441\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 TextMeasurer<\/p>\n<p>\u0418\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0441 \u0446\u0438\u0444\u0435\u0440\u0431\u043b\u0430\u0442\u043e\u043c \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">private val digits = listOf(\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"0\")  @Composable fun OldPhone(     modifier: Modifier, ) {     var rotationAngle by remember { mutableFloatStateOf(0f) }     val textMeasurer = rememberTextMeasurer()     val typography = MaterialTheme.typography.headlineLarge      Canvas(         modifier = modifier.aspectRatio(1f)     ) {         rotate(rotationAngle) {             drawCircle(                 color = Color.White,             )              val angleStep = -270f \/ digits.size             val center = Offset(size.width \/ 2, size.height \/ 2)             val radius = size.width \/ 2f - 20.dp.toPx()              digits.forEachIndexed { index, digit -&gt;                 val angleInDegrees = index * angleStep - 45                 val angleInRadians = Math.toRadians(angleInDegrees.toDouble())                  val x = center.x + radius * cos(angleInRadians).toFloat()                 val y = center.y + radius * sin(angleInRadians).toFloat()                  val textLayoutResult: TextLayoutResult = textMeasurer.measure(                     text = digit,                     style = typography,                 )                  drawText(                     textLayoutResult = textLayoutResult,                     topLeft = Offset(                         x - textLayoutResult.size.width \/ 2f,                         y - textLayoutResult.size.height \/ 2f,                     ),                 )             }         }     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0410 \u0432\u043e\u0442 \u0442\u0430\u043a \u0432\u0441\u0435 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442<\/p>\n<details class=\"spoiler\">\n<summary>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u0434\u0435\u043b\u0430\u043d\u043e \u043d\u0435 \u0442\u0430\u043a \u043c\u043d\u043e\u0433\u043e, \u043d\u043e \u0443\u0436\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u043e\u0445\u043e\u0436\u0435, \u043d\u0435 \u043f\u0440\u0430\u0432\u0434\u0430 \u043b\u0438?<\/p>\n<h2>\u0412\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u043e\u0433\u043e \u0434\u0438\u0441\u043a\u0430<\/h2>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0443\u0433\u043b\u0430 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u2014 \u0432\u0430\u0436\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0434\u0438\u0441\u043a\u0430 \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430. \u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0443\u0433\u043e\u043b \u043c\u0435\u0436\u0434\u0443 \u043b\u0438\u043d\u0438\u0435\u0439 \u043e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u043a\u0440\u0443\u0433\u0430 \u043a \u0442\u043e\u0447\u043a\u0435 \u043a\u0430\u0441\u0430\u043d\u0438\u044f \u043f\u0430\u043b\u044c\u0446\u0430 \u0438 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0441\u044c\u044e \u043a\u0440\u0443\u0433\u0430. \u042d\u0442\u043e\u0442 \u0443\u0433\u043e\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0441\u043a \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u043b\u0441\u044f.<\/p>\n<p>\u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<details class=\"spoiler\">\n<summary>\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"\"><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u043f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u043a\u043e\u0434, \u0430 \u043f\u043e\u0442\u043e\u043c \u0440\u0430\u0437\u0431\u0435\u0440\u0443, \u0447\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u043d\u0435\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">@Composable fun OldPhone(     modifier: Modifier, ) {     var rotationAngle by remember { mutableFloatStateOf(0f) }      \/\/ \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0434      Canvas(         modifier = modifier             .aspectRatio(1f)             .pointerInput(Unit) {                 awaitEachGesture {                     val down = awaitFirstDown()                     var currentAngle = calculateAngle(down.position, size.center)                      do {                         val event = awaitPointerEvent()                         val newAngle = calculateAngle(event.changes.first().position, size.center)                         val deltaAngle = newAngle - currentAngle                         rotationAngle = (rotationAngle + deltaAngle)                         currentAngle = newAngle                         event.changes.first().consume()                     } while (event.changes.any { it.pressed })                 }             }     ) {         rotate(rotationAngle) {             \/\/ \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0434         }     } }  private fun calculateAngle(position: Offset, center: IntOffset): Float {     val dx = position.x - center.x     val dy = position.y - center.y     return Math.toDegrees(atan2(dy.toDouble(), dx.toDouble())).toFloat() }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u0430\u043a \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0443\u0433\u043e\u043b?<\/p>\n<p>\u041f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0438 \u043f\u0430\u043b\u044c\u0446\u0430 \u043c\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0440\u0430\u0437\u043d\u0438\u0446\u0443 \u043f\u043e \u043e\u0441\u044f\u043c X \u0438 Y:<\/p>\n<pre><code class=\"kotlin\">val dx = position.x - center.x \/\/ \u0421\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u043e X val dy = position.y - center.y \/\/ \u0421\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u043e Y<\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442 \u0432\u0435\u043a\u0442\u043e\u0440 \u043e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u043a\u0440\u0443\u0433\u0430 \u043a \u0442\u043e\u0447\u043a\u0435 \u043a\u0430\u0441\u0430\u043d\u0438\u044f. \u0421 \u044d\u0442\u0438\u043c \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u043c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0443\u0433\u043e\u043b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 atan2:<\/p>\n<pre><code class=\"kotlin\">val angleInRadians = atan2(dy.toDouble(), dx.toDouble())<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0443\u0433\u043e\u043b \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0435 \u043e\u0442 \u2212180 \u0434\u043e 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432.<\/p>\n<p>\u041a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0443\u0433\u043e\u043b?<\/p>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043f\u0430\u043b\u0435\u0446 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f, \u043c\u044b \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u0440\u0430\u0437\u043d\u0438\u0446\u0443 \u043c\u0435\u0436\u0434\u0443 \u043d\u043e\u0432\u044b\u043c \u0443\u0433\u043b\u043e\u043c \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u043c:<\/p>\n<pre><code class=\"kotlin\">val deltaAngle = newAngle - currentAngle rotationAngle += deltaAngle currentAngle = newAngle<\/code><\/pre>\n<ul>\n<li>\n<p>deltaAngle \u2014 \u044d\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0443\u0433\u043b\u0430, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0441\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 deltaAngle \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439, \u0434\u0438\u0441\u043a \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u2014 \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a \u043a\u0440\u0430\u0442\u043a\u043e \u043c\u043e\u0436\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c<\/p>\n<ol>\n<li>\n<p>\u0412 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u0430\u0441\u0430\u043d\u0438\u044f \u044d\u043a\u0440\u0430\u043d\u0430, \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439 \u0443\u0433\u043e\u043b<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0438 \u043f\u0430\u043b\u044c\u0446\u0430 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0439 \u0443\u0433\u043e\u043b<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0443\u0433\u043e\u043b \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p>\u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u0438\u0441\u043a\u0443 \u043f\u043b\u0430\u0432\u043d\u043e \u0432\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043f\u0430\u043b\u044c\u0446\u0430.<\/p>\n<\/li>\n<\/ol>\n<p>\u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0434\u0435\u043b\u0430\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0438\u043d\u0442\u0443\u0438\u0442\u0438\u0432\u043d\u044b\u043c \u0438 \u0442\u043e\u0447\u043d\u044b\u043c.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/p>\n<details class=\"spoiler\">\n<summary>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u043d\u0435 \u0443\u0436\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f!<\/p>\n<h2>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u0436\u0430\u0442\u043e\u0439 \u0446\u0438\u0444\u0440\u044b \u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430<\/h2>\n<p>\u0421\u0435\u0439\u0447\u0430\u0441 \u0443 \u043d\u0430\u0448\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 &#8212; \u0434\u0438\u0441\u043a \u043c\u043e\u0436\u043d\u043e \u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0432 \u043b\u044e\u0431\u044b\u0445 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u0445 \u0431\u0435\u0437 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439, \u043d\u043e \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u0442\u0435\u043b\u0435\u0444\u043e\u043d \u0442\u0430\u043a \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c!<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c deltaAngle, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u0440\u0438 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0438 \u0440\u0430\u0437\u043d\u0438\u0446\u044b \u043c\u0435\u0436\u0434\u0443 \u0434\u0432\u0443\u043c\u044f \u0443\u0433\u043b\u0430\u043c\u0438, \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 180 \u0438\u043b\u0438 \u043c\u0435\u043d\u044c\u0448\u0435 \u2212180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432 \u0438\u0437-\u0437\u0430 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0447\u0435\u0440\u0435\u0437 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u2212180 \u0438 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u041d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0438\u0440\u0443\u0435\u0442 \u0442\u0430\u043a\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f:<\/p>\n<ul>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0440\u0430\u0437\u043d\u0438\u0446\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0432\u044b\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f 360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0443\u0433\u043e\u043b \u0432 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043c\u0435\u043d\u044c\u0448\u0435 \u2212180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f 360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"kotlin\">val deltaAngle = (newAngle - currentAngle).let { delta -&gt;     when {         delta &gt; 180f -&gt; delta - 360f         delta &lt; -180f -&gt; delta + 360f         else -&gt; delta     } }<\/code><\/pre>\n<p>\u0414\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0446\u0438\u0444\u0440 \u0440\u0430\u0432\u0435\u043d 270 \u0433\u0440\u0430\u0434\u0443\u0441\u0430\u043c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u0442 \u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430 \u0440\u0430\u0432\u043d\u044b\u043c\u0438 \u043e\u0442 -270 \u0434\u043e 270 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c, \u044f \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u0434\u0435\u043b\u044c\u0442\u0443 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435<\/p>\n<pre><code class=\"kotlin\">var accumulatedDelta by remember { mutableFloatStateOf(0f) }  val targetRotation = rotationAngle + deltaAngle  val canRotate = (accumulatedDelta &lt;= 270f &amp;&amp; deltaAngle &gt; 0) ||                 (accumulatedDelta &gt;= -270f &amp;&amp; deltaAngle &lt; 0) if (canRotate) {     rotationAngle = targetRotation     accumulatedDelta += deltaAngle }<\/code><\/pre>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u043d\u0430\u043a\u0430 deltaAngle \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0434\u0438\u0441\u043a\u0430:<\/p>\n<ul>\n<li>\n<p>deltaAngle &lt; 0 &#8212; \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438<\/p>\n<\/li>\n<li>\n<p>deltaAngle &gt; 0 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0439\u043c\u0435\u043c\u0441\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043c \u0442\u043e\u0433\u043e, \u043a\u0430\u043a\u0430\u044f \u0446\u0438\u0444\u0440\u0430 \u0431\u044b\u043b\u0430 \u043d\u0430\u0436\u0430\u0442\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u044f \u0434\u043e\u0431\u0430\u0432\u043b\u044e \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 DigitBounds. \u042d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0433\u0440\u0430\u043d\u0438\u0446 \u043a\u0430\u0436\u0434\u043e\u0439 \u0446\u0438\u0444\u0440\u044b.<\/p>\n<pre><code class=\"kotlin\">private class DigitBounds(     var left: Float = 0f,     var top: Float = 0f,     var right: Float = 0f,     var bottom: Float = 0f ) {     fun contains(point: Offset): Boolean {         return point.x &gt;= left &amp;&amp; point.x &lt;= right &amp;&amp;                 point.y &gt;= top &amp;&amp; point.y &lt;= bottom     }      fun update(x: Float, y: Float, size: Float) {         left = x - size \/ 2         top = y - size \/ 2         right = x + size \/ 2         bottom = y + size \/ 2     } }<\/code><\/pre>\n<ul>\n<li>\n<p><strong>\u041c\u0435\u0442\u043e\u0434 <\/strong>contains:<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043b\u0438 \u0442\u043e\u0447\u043a\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u0433\u0440\u0430\u043d\u0438\u0446 \u0446\u0438\u0444\u0440\u044b<\/p>\n<\/li>\n<li>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043d\u0430\u0436\u0430\u0442\u043e\u0439 \u0446\u0438\u0444\u0440\u044b<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>\u041c\u0435\u0442\u043e\u0434 <\/strong>update:<\/p>\n<ul>\n<li>\n<p>\u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u0446\u0438\u0444\u0440\u044b \u043f\u0440\u0438 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0435, \u043e\u0441\u043d\u043e\u0432\u044b\u0432\u0430\u044f\u0441\u044c \u043d\u0430 \u0435\u0451 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 (x, y) \u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0430\u0445<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>\u0412\u0441\u0435 \u043f\u043e\u043b\u044f \u043a\u043b\u0430\u0441\u0441\u0430 DigitBounds &#8212; \u043c\u0443\u0442\u0430\u0431\u0435\u043b\u044c\u043d\u044b\u0435, \u044d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u0442 \u043a \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043c\u0443\u0441\u043e\u0440\u0430<\/p>\n<p>\u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u044f \u0441\u043e\u0437\u0434\u0430\u044e \u0441\u043f\u0438\u0441\u043e\u043a DigitBounds \u0440\u0430\u0432\u043d\u044b\u0439 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0443 \u0446\u0438\u0444\u0440<\/p>\n<pre><code class=\"kotlin\">val digitBoundsList = remember { List(digits.size) { DigitBounds() } }<\/code><\/pre>\n<p>\u0410 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u044e \u044d\u0442\u043e\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0443\u0436\u0435 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u043e\u0442\u0440\u0438\u0441\u043e\u043a\u0438<\/p>\n<pre><code class=\"kotlin\">digits.forEachIndexed { index, _ -&gt;     val angleInDegrees = index * angleStep - 45     val angleInRadians =<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-444269","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/444269","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=444269"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/444269\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=444269"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=444269"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=444269"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}