انتقال عنصر مشترک را سفارشی کنید

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

مشخصات انیمیشن

برای تغییر مشخصات انیمیشن مورد استفاده برای اندازه و موقعیت حرکت، می‌توانید پارامتر boundsTransform متفاوتی را در Modifier.sharedElement() تعیین کنید. این پارامتر موقعیت اولیه Rect و موقعیت هدف Rect را فراهم می‌کند.

برای مثال، برای اینکه متن در مثال قبلی با حرکت قوسی حرکت کند، پارامتر boundsTransform را برای استفاده از مشخصات keyframes مشخص کنید:

val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
    keyframes {
        durationMillis = boundsAnimationDurationMillis
        initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
        targetBounds at boundsAnimationDurationMillis
    }
}
Text(
    "Cupcake", fontSize = 28.sp,
    modifier = Modifier.sharedBounds(
        rememberSharedContentState(key = "title"),
        animatedVisibilityScope = animatedVisibilityScope,
        boundsTransform = textBoundsTransform
    )
)

شما می‌توانید از هر AnimationSpec استفاده کنید. این مثال از مشخصات keyframes استفاده می‌کند.

شکل ۱. مثالی که پارامترهای مختلف boundsTransform را نشان می‌دهد

حالت تغییر اندازه

هنگام انیمیشن‌سازی بین دو مرز مشترک، می‌توانید پارامتر resizeMode را روی RemeasureToBounds یا ScaleToBounds تنظیم کنید. این پارامتر نحوه‌ی انتقال عنصر مشترک بین دو حالت را تعیین می‌کند. ScaleToBounds ابتدا طرح‌بندی فرزند را با محدودیت‌های lookahead (یا target) اندازه‌گیری می‌کند. سپس، طرح‌بندی پایدار فرزند برای قرار گرفتن در مرزهای مشترک مقیاس‌بندی می‌شود. ScaleToBounds می‌توان به عنوان یک "مقیاس گرافیکی" بین حالت‌ها در نظر گرفت.

در مقابل، RemeasureToBounds طرح‌بندی فرزند sharedBounds را با محدودیت‌های ثابت متحرک بر اساس اندازه هدف، دوباره اندازه‌گیری و طرح‌بندی می‌کند. این اندازه‌گیری مجدد با تغییر اندازه مرزها، که می‌تواند به طور بالقوه هر فریم باشد، آغاز می‌شود.

برای ترکیب‌های Text ، ScaleToBounds توصیه می‌شود، زیرا از انتقال مجدد و پخش مجدد متن روی خطوط مختلف جلوگیری می‌کند. RemeasureToBounds برای مرزهایی که نسبت ابعاد متفاوتی دارند و اگر می‌خواهید پیوستگی روان بین دو عنصر مشترک وجود داشته باشد، توصیه می‌شود.

تفاوت بین دو حالت تغییر اندازه را می‌توان در مثال‌های زیر مشاهده کرد:

ScaleToBounds

RemeasureToBounds

رفتن به طرح نهایی

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

مثال زیر، متن توضیحی "Lorem Ipsum" را که به دو روش مختلف وارد صفحه می‌شود، نشان می‌دهد. در مثال اول، متن هنگام ورود با افزایش اندازه‌ی ظرف، دوباره جریان می‌یابد. در مثال دوم، متن با افزایش اندازه، دوباره جریان پیدا نمی‌کند. اضافه کردن Modifier.skipToLookaheadSize() از جریان دوباره هنگام افزایش اندازه جلوگیری می‌کند.

بدون Modifier.skipToLookahead() - به جریان مجدد متن "Lorem Ipsum" توجه کنید

Modifier.skipToLookahead() - توجه داشته باشید که متن "Lorem Ipsum" در ابتدای انیمیشن حالت نهایی خود را حفظ می‌کند.

کلیپ و پوشش

برای اینکه عناصر مشترک بین composable های مختلف به اشتراک گذاشته شوند، رندر composable هنگام شروع انتقال به تطابق خود در مقصد، به یک لایه overlay ارتقا می‌یابد . تأثیر این امر این است که از مرزهای والد و تبدیل‌های لایه آن (به عنوان مثال، آلفا و مقیاس) فرار می‌کند.

این عنصر روی سایر عناصر رابط کاربری غیرمشترک رندر می‌شود. پس از پایان انتقال، عنصر از روی لایه پوششی به DrawScope مخصوص خود رها می‌شود.

برای چسباندن یک عنصر مشترک به یک شکل، از تابع استاندارد Modifier.clip() استفاده کنید. آن را بعد از sharedElement() قرار دهید:

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

اگر نیاز دارید که مطمئن شوید یک عنصر مشترک هرگز خارج از کانتینر والد رندر نمی‌شود، می‌توانید clipInOverlayDuringTransition روی sharedElement() تنظیم کنید. به طور پیش‌فرض، برای مرزهای مشترک تو در تو، clipInOverlayDuringTransition از مسیر کلیپ از sharedBounds() والد استفاده می‌کند.

برای پشتیبانی از نگه داشتن عناصر رابط کاربری خاص، مانند نوار پایین یا دکمه عملیاتی شناور، همیشه در بالا در طول انتقال یک عنصر مشترک، از Modifier.renderInSharedTransitionScopeOverlay() استفاده کنید. به طور پیش‌فرض، این اصلاح‌کننده محتوا را در طول زمانی که انتقال مشترک فعال است، در پوشش نگه می‌دارد.

برای مثال، در Jetsnack، BottomAppBar باید تا زمانی که صفحه نمایش قابل مشاهده نباشد، روی عنصر مشترک قرار گیرد. اضافه کردن این اصلاح‌کننده به composable آن را در حالت بالا نگه می‌دارد.

بدون Modifier.renderInSharedTransitionScopeOverlay()

با استفاده از Modifier.renderInSharedTransitionScopeOverlay()

ممکن است بخواهید که عنصر ترکیبی غیراشتراکی شما قبل از انتقال، علاوه بر اینکه روی سایر عناصر ترکیبی باقی بماند، متحرک هم باشد. در چنین مواردی، از renderInSharedTransitionScopeOverlay().animateEnterExit() برای متحرک‌سازی عنصر ترکیبی هنگام اجرای انتقال عنصر مشترک استفاده کنید:

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

شکل ۲. نوار برنامه پایین که با تغییر انیمیشن به داخل و خارج حرکت می‌کند.

در موارد نادری که نمی‌خواهید عنصر اشتراکی شما به صورت overlay رندر شود، می‌توانید مقدار renderInOverlayDuringTransition را در sharedElement() روی false تنظیم کنید.

اطلاع‌رسانی به طرح‌بندی‌های خواهر و برادر از تغییرات در اندازه عنصر مشترک

به طور پیش‌فرض، sharedBounds() و sharedElement() هیچ تغییر اندازه‌ای را هنگام انتقال طرح‌بندی به کانتینر والد اطلاع نمی‌دهند.

برای اینکه تغییرات اندازه در کانتینر والد همزمان با انتقال اعمال شود، پارامتر placeHolderSize را به PlaceHolderSize.animatedSize تغییر دهید. انجام این کار باعث می‌شود که آیتم بزرگ یا کوچک شود. سایر آیتم‌های موجود در طرح‌بندی به این تغییر واکنش نشان می‌دهند.

PlaceholderSize.contentSize (پیش‌فرض)

PlaceholderSize.animatedSize

(دقت کنید که چگونه سایر موارد موجود در لیست در پاسخ به افزایش یک مورد، به سمت پایین حرکت می‌کنند)