Jetpack Compose Swipe Cards – إنشاء بطاقات متحركة قابلة للسحب في Android وiOS
إذا كنت تعمل على تطبيق Android أو iOS وتريد إنشاء واجهة تحتوي على بطاقات متراكبة يمكن سحبها يمينًا أو يسارًا أو للأعلى والأسفل، مثل البطاقات الموجودة في تطبيقات التعارف أو المحافظ البنكية أو منصات مشاهدة الأفلام، فإن مكتبة SwipingCards توفر طريقة سهلة لتنفيذ ذلك باستخدام Kotlin وCompose Multiplatform.
تتميز المكتبة بأنها لا تقوم بحذف البطاقة عند سحبها، بل تنقلها إلى نهاية مجموعة البطاقات، مما يسمح للمستخدم بالاستمرار في تصفحها بشكل دائري، مع تأثيرات ثلاثية الأبعاد وحركات Spring Animation تمنح الواجهة مظهرًا سلسًا واحترافيًا.
في هذا المقال سنتعرف على طريقة استخدام مكتبة SwipingCards لإنشاء Jetpack Compose Swipe Cards، وكيفية تخصيص شكل البطاقات والتعامل مع اتجاهات السحب وعرض العناصر داخل تطبيقات Android وiOS.
ما هي مكتبة SwipingCards؟
SwipingCards هي مكتبة مفتوحة المصدر من تطوير Maxim Kachinkin، مخصصة لإنشاء مجموعة بطاقات متراكبة وقابلة للسحب داخل تطبيقات Compose Multiplatform.
تعتمد المكتبة على مبدأ Circular Card Deck، بحيث يتم نقل البطاقة الأولى إلى نهاية المجموعة عند سحبها بدلًا من حذفها نهائيًا.
كما تدعم المكتبة عرض البطاقات باستخدام أي Composable، ولذلك يمكنك إنشاء بطاقة تحتوي على صورة، أو بيانات حساب بنكي، أو معلومات منتج، أو حتى واجهة كاملة.
مميزات مكتبة SwipingCards
- دعم Android وiOS من خلال Compose Multiplatform.
- سحب البطاقات في أربعة اتجاهات.
- إرجاع البطاقة المسحوبة إلى نهاية القائمة.
- دعم عدد غير محدود من دورات تصفح البطاقات.
- تأثير دوران ثلاثي الأبعاد 3D Rotation.
- حركات Spring Animation.
- تفاعل البطاقات الخلفية مع حركة البطاقة الأمامية.
- دعم السحب السريع Fling Gestures.
- Haptic Feedback عند تجاوز حد السحب.
- إمكانية تحديد عدد البطاقات الظاهرة.
- إمكانية التحكم في زاوية دوران البطاقة.
- تخصيص شكل البطاقة بالكامل.
- التعامل مع تغييرات القائمة الخارجية باستخدام Stable Keys.
- إرجاع نتيجة السحب واتجاهه والعناصر بعد إعادة ترتيبها.
كيف تعمل SwipingCards؟
لنفترض أن لدينا خمس بطاقات مرتبة بالشكل التالي:
Card 1
Card 2
Card 3
Card 4
Card 5عندما يسحب المستخدم البطاقة الأولى، يتم نقلها إلى نهاية القائمة:
Card 2
Card 3
Card 4
Card 5
Card 1وعندما يسحب بطاقة أخرى تصبح القائمة:
Card 3
Card 4
Card 5
Card 1
Card 2وهكذا يمكن للمستخدم تصفح البطاقات باستمرار دون الوصول إلى نهاية ثابتة للمجموعة.
تثبيت مكتبة SwipingCards
المكتبة متاحة من خلال Maven Central بالإصدار:
0.1.0يمكن إضافتها إلى مشروع Compose Multiplatform داخل ملف build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation(
"io.github.makzimi:swipingcards:0.1.0"
)
}
}
}إذا كنت تستخدم مشروع Android فقط، يمكنك إضافة الاعتمادية داخل قسم dependencies في وحدة التطبيق، مع التأكد من إعداد مستودع mavenCentral().
dependencies {
implementation(
"io.github.makzimi:swipingcards:0.1.0"
)
}ملاحظة: يشير توثيق المكتبة إلى أن الحد الأدنى لإصدار Android المطلوب هو API 33، أي Android 13، لذلك يجب مراعاة ذلك قبل إضافتها إلى مشروع يدعم إصدارات أقدم.
إنشاء Model خاص بالبطاقات
قبل استخدام المكتبة نحتاج إلى إنشاء Model يمثل البيانات التي ستظهر داخل كل بطاقة.
data class CardItem(
val id: Int,
val title: String,
val description: String,
val color: Color
)في هذا المثال تحتوي كل بطاقة على معرف فريد وعنوان ووصف ولون للخلفية.
يمكن إنشاء مجموعة من البطاقات:
val cards = listOf(
CardItem(
id = 1,
title = "Card One",
description = "First Card",
color = Color(0xFF6366F1)
),
CardItem(
id = 2,
title = "Card Two",
description = "Second Card",
color = Color(0xFF10B981)
),
CardItem(
id = 3,
title = "Card Three",
description = "Third Card",
color = Color(0xFFF59E0B)
)
)إنشاء أول Swipe Cards باستخدام Jetpack Compose
بعد تجهيز البيانات يمكن استخدام Composable الرئيسية:
SwipingCards()وتمرير قائمة البطاقات إليها:
@Composable
fun CardsScreen() {
val cards = remember {
listOf(
CardItem(
1,
"Flutter",
"Mobile Development",
Color.Blue
),
CardItem(
2,
"Kotlin",
"Android Development",
Color.Magenta
),
CardItem(
3,
"Compose",
"Modern Android UI",
Color.Cyan
)
)
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
SwipingCards(
cards = cards,
key = { it.id },
modifier = Modifier
.fillMaxWidth(0.8f)
.aspectRatio(2f / 3f),
maxVisibleCards = 3
) { item ->
MyCard(item)
}
}
}بهذا الشكل أنشأنا مجموعة من البطاقات القابلة للسحب، مع الاستفادة من الحركات والإيماءات التي توفرها المكتبة.
إنشاء تصميم مخصص للبطاقة
الميزة المهمة في SwipingCards أنها لا تفرض تصميمًا محددًا للبطاقة.
يمكن إنشاء Composable خاصة بنا:
@Composable
fun MyCard(
item: CardItem
) {
Box(
modifier = Modifier
.fillMaxSize()
.clip(
RoundedCornerShape(24.dp)
)
.background(item.color),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment =
Alignment.CenterHorizontally
) {
Text(
text = item.title,
color = Color.White,
fontSize = 28.sp,
fontWeight = FontWeight.Bold
)
Spacer(
modifier = Modifier.height(12.dp)
)
Text(
text = item.description,
color = Color.White,
fontSize = 16.sp
)
}
}
}ستقوم المكتبة بعرض هذه البطاقة داخل المجموعة والتعامل مع حركة السحب والدوران وترتيب البطاقات تلقائيًا.
ما وظيفة key في SwipingCards؟
من أهم الخصائص التي يجب تمريرها إلى المكتبة:
key = { it.id }تُستخدم هذه القيمة للتعرف على هوية كل بطاقة، حتى إذا تغير ترتيب القائمة.
يجب أن تكون قيمة key ثابتة وفريدة لكل عنصر.
على سبيل المثال، لا يُفضل الاعتماد على موقع البطاقة داخل القائمة إذا كان ترتيب العناصر يتغير باستمرار.
لأن المكتبة تستخدم هذه المعرفات لمزامنة الترتيب الداخلي مع القائمة التي يوفرها التطبيق.
التحكم في عدد البطاقات الظاهرة
يمكن استخدام الخاصية:
maxVisibleCardsلتحديد عدد البطاقات التي تظهر في الواجهة في نفس الوقت.
مثلًا:
SwipingCards(
cards = cards,
key = { it.id },
maxVisibleCards = 4
) { item ->
MyCard(item)
}هنا يتم عرض أربع بطاقات كحد أقصى داخل المجموعة المرئية.
أما البطاقات الإضافية، فتبقى ضمن قائمة داخلية غير ظاهرة، وتنتقل تدريجيًا إلى المقدمة عندما يتم سحب البطاقات الحالية.
تخصيص حجم البطاقات
لا تفرض المكتبة عرضًا أو ارتفاعًا ثابتًا، بل تعتمد على القيود التي نمررها من خلال:
modifierلإنشاء بطاقة عمودية:
Modifier
.fillMaxWidth(0.8f)
.aspectRatio(2f / 3f)أما لإنشاء بطاقة بنكية أفقية:
Modifier
.fillMaxWidth(0.9f)
.aspectRatio(1.586f)وهذا يجعل المكتبة مناسبة لواجهات مختلفة دون الحاجة إلى تعديل منطق الحركة نفسه.
إضافة تأثير 3D Rotation
واحدة من أهم مميزات المكتبة وجود تأثير ثلاثي الأبعاد أثناء سحب البطاقة.
فعندما يبدأ المستخدم بتحريك البطاقة، فإنها لا تنتقل فقط أفقيًا أو عموديًا، بل تميل بمنظور ثلاثي الأبعاد.
يمكن التحكم في أقصى زاوية باستخدام:
maxRotationYمثال:
SwipingCards(
cards = cards,
key = { it.id },
maxRotationY = 45f
) { item ->
MyCard(item)
}القيمة الافتراضية في المكتبة هي:
38fوكلما تغيرت القيمة يتغير مقدار الميلان الأقصى المسموح أثناء السحب.
تحديد المسافة المطلوبة لتنفيذ Swipe
يمكنك التحكم في المسافة التي يجب أن تقطعها البطاقة قبل اعتماد السحب.
وذلك باستخدام:
swipeThresholdFractionالقيمة الافتراضية:
0.20fأي إن تجاوز إزاحة السحب نحو 20% من عرض مجموعة البطاقات يمكن أن يؤدي إلى اعتماد عملية السحب.
مثال:
SwipingCards(
cards = cards,
key = { it.id },
swipeThresholdFraction = 0.25f
) { item ->
MyCard(item)
}هنا جعلنا المسافة المطلوبة تساوي 25% من عرض المجموعة.
كما تدعم المكتبة السحب السريع Fling، والذي يمكن أن يؤدي إلى اعتماد السحب بناءً على سرعة الحركة.
معرفة اتجاه السحب
توفر المكتبة Callback باسم:
onSwipeيتم استدعاؤها بعد اعتماد عملية السحب.
SwipingCards(
cards = cards,
key = { it.id },
onSwipe = { result ->
println(
result.direction
)
}
) { item ->
MyCard(item)
}يمكن أن تحتوي نتيجة الاتجاه على إحدى القيم التالية:
SwipeDirection.Left
SwipeDirection.Right
SwipeDirection.Up
SwipeDirection.Downوبذلك يمكنك تنفيذ إجراءات مختلفة حسب الاتجاه.
تنفيذ عملية مختلفة لكل اتجاه
على سبيل المثال، لنفترض أنك تريد إنشاء واجهة مشابهة لتطبيقات التعارف.
يمكنك التعامل مع السحب بهذا الشكل:
onSwipe = { result ->
when (result.direction) {
SwipeDirection.Left -> {
println("Dislike")
}
SwipeDirection.Right -> {
println("Like")
}
SwipeDirection.Up -> {
println("Super Like")
}
SwipeDirection.Down -> {
println("Skip")
}
}
}هذا السلوك من اختيار التطبيق؛ فالمكتبة نفسها لا تفرض معنى معينًا لكل اتجاه.
ما هو SwipeResult؟
عندما تنتهي عملية السحب تعيد المكتبة كائنًا من نوع:
SwipeResult<T>ويحتوي على البيانات التالية:
data class SwipeResult<T>(
val card: T,
val key: Any,
val direction: SwipeDirection,
val resultingOrder: List<T>
)كل قيمة لها وظيفة مختلفة.
card
يمثل العنصر الذي قام المستخدم بسحبه.
result.cardkey
يمثل المعرف الفريد للبطاقة.
result.keydirection
يمثل اتجاه السحب.
result.directionresultingOrder
يمثل ترتيب البطاقات بعد نقل البطاقة المسحوبة إلى نهاية المجموعة.
result.resultingOrderهذه الخاصية مفيدة إذا أردت الاحتفاظ بترتيب البطاقات بعد تفاعل المستخدم معها.
حفظ ترتيب البطاقات بعد السحب
يمكنك الاحتفاظ بقائمة البطاقات داخل State وتحديثها عند انتهاء عملية السحب.
var cards by remember {
mutableStateOf(initialCards)
}
SwipingCards(
cards = cards,
key = { it.id },
onSwipe = { result ->
cards = result.resultingOrder
}
) { item ->
MyCard(item)
}الميزة المهمة أن SwipingCards تتعامل مع هذا التحديث باعتباره تأكيدًا للترتيب الجديد، ولا تحتاج إلى إعادة تشغيل الحركة من البداية.
هل يمكن إضافة أو حذف بطاقات أثناء التشغيل؟
نعم، تدعم المكتبة تغيير قائمة العناصر الخارجية.
إذا أضفت بطاقة جديدة أو حذفت بطاقة موجودة، تحاول المكتبة تحديث مجموعتها الداخلية مع الحفاظ على الحركات الجارية.
لكن إذا تم تغيير ترتيب العناصر الباقية خارجيًا، يتم إعادة وضعها حسب الترتيب الجديد دون Animation انتقالية لهذا التغيير الخارجي.
وهذا مفيد جدًا عند استخدام State Management أو تحديث البيانات من Backend.
ما هي Background Repulsion؟
من المؤثرات التي تقدمها المكتبة تأثير يسمى:
Background Repulsionعندما يبدأ المستخدم بسحب البطاقة الأمامية، تتحرك البطاقات الموجودة خلفها بشكل تفاعلي بدل البقاء ثابتة تمامًا.
يعطي ذلك إحساسًا بأن جميع البطاقات مرتبطة بحركة واحدة، مما يجعل التجربة أكثر طبيعية.
Spring Animation في SwipingCards
تعتمد المكتبة على حركات Spring لإرجاع البطاقة إلى مكانها أو نقل البطاقات إلى مواقعها الجديدة.
إذا قام المستخدم بسحب البطاقة مسافة قصيرة دون الوصول إلى حد اعتماد السحب، فإنها تعود إلى مكانها بحركة ناعمة.
أما إذا اكتملت عملية السحب، فتتحرك البطاقة إلى الخارج ثم تنتقل إلى نهاية المجموعة مع تقدم البطاقة التالية.
دعم Haptic Feedback
توفر المكتبة اهتزازًا تفاعليًا عندما تصل حركة السحب إلى الحد المطلوب، وكذلك عند اعتماد السحب.
تساعد هذه الخاصية المستخدم على الشعور باستجابة الواجهة بدل الاعتماد على الحركة البصرية فقط.
يعتمد الإحساس الفعلي بالاهتزاز على الجهاز والمنصة وإعدادات المستخدم.
استخدام SwipingCards لعرض بطاقات بنكية
يمكن استخدام المكتبة لإنشاء محفظة تحتوي على مجموعة بطاقات دفع متراكبة.
مثلًا:
data class BankCard(
val id: String,
val cardHolder: String,
val lastFourDigits: String,
val color: Color
)ثم عرض العناصر:
SwipingCards(
cards = bankCards,
key = { it.id },
modifier = Modifier
.fillMaxWidth(0.9f)
.aspectRatio(1.586f),
maxVisibleCards = 3,
onSwipe = { result ->
println(
"Selected: ${result.card.id}"
)
}
) { card ->
BankCardView(card)
}وبذلك يستطيع المستخدم التنقل بين بطاقاته بسحب البطاقة الموجودة في المقدمة.
إنشاء تصميم بطاقة بنكية
يمكن بناء البطاقة باستخدام تدرج لوني وأيقونات ونصوص:
@Composable
fun BankCardView(card: BankCard) {
Box(
modifier = Modifier
.fillMaxSize()
.clip(
RoundedCornerShape(20.dp)
)
.background(
Brush.linearGradient(
colors = listOf(
card.color,
Color.Black
)
)
)
.padding(24.dp)
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement =
Arrangement.SpaceBetween
) {
Text(
text = "BANK CARD",
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Text(
text =
"•••• •••• •••• ${card.lastFourDigits}",
color = Color.White,
fontSize = 22.sp
)
Text(
text = card.cardHolder,
color = Color.White
)
}
}
}هذا المثال يقدم تصميمًا مبسطًا ويمكن تطويره بإضافة شعار البنك وأيقونة الشريحة وأنواع البطاقات المختلفة.
استخدام SwipingCards في تطبيقات الأفلام
يمكن أيضًا استخدامها لإنشاء قائمة أفلام تشبه بطاقات منصات البث.
مثل:
data class Movie(
val id: Int,
val title: String,
val posterUrl: String
)ثم:
SwipingCards(
cards = movies,
key = { it.id },
modifier = Modifier
.fillMaxWidth(0.8f)
.aspectRatio(2f / 3f),
maxVisibleCards = 4
) { movie ->
MoviePosterCard(movie)
}يمكن تنفيذ MoviePosterCard باستخدام أي مكتبة عرض صور مناسبة لمشروعك، مثل Coil على Android.
ويمنح هذا الأسلوب المستخدم طريقة تفاعلية لتصفح الأفلام بدل الاعتماد على قائمة تقليدية فقط.
الأمثلة المتوفرة داخل المشروع
يوفر المستودع أربعة أمثلة رئيسية توضح استخدام المكتبة في واجهات مختلفة.
1. Dating Cards
مجموعة من خمس بطاقات عمودية بنسبة أبعاد 2:3، تحتوي على صور وبيانات وعناصر تفاعلية مناسبة لتطبيقات التعارف.
2. Bank Cards
مجموعة من ثلاث بطاقات بنكية أفقية بنسبة أبعاد 1.586:1، مع تأثيرات Gradient وعناصر البطاقة المالية.
3. D&D Cards
مجموعة من عشر بطاقات مستوحاة من ألعاب تقمص الأدوار، تتضمن صورًا وإحصائيات وعناصر مرئية مختلفة.
4. Streaming Cards
مجموعة من ست بطاقات لعرض محتوى ترفيهي وأفلام، تشبه ملصقات خدمات البث، مع أزرار مثل Play وMy List.
هيكل المشروع
يتكون المستودع من عدة Modules رئيسية:
SwipingCards/
│
├── swipingcards/
│
├── samples/
│ ├── shared/
│ ├── androidApp/
│ └── iosApp/
│
├── build.gradle.kts
│
├── settings.gradle.kts
│
└── README.mdالمجلد swipingcards يحتوي على المكتبة، بينما يحتوي samples/shared على أمثلة الواجهات المشتركة بين Android وiOS.
تشغيل المشروع على Android
يمكن تنزيل المستودع باستخدام Git:
git clone https://github.com/makzimi/SwipingCards.git
cd SwipingCardsثم فتح المشروع باستخدام Android Studio وتشغيل تطبيق الأمثلة، أو تنفيذ:
./gradlew :samples:androidApp:installDebugيجب التأكد من إعداد Android SDK وتوصيل جهاز متوافق أو تشغيل Emulator مناسب.
تشغيل المشروع على iOS
يدعم المشروع أيضًا iOS باستخدام Compose Multiplatform.
بعد تنزيل المستودع يمكنك فتح:
samples/iosAppباستخدام Xcode وتشغيل التطبيق على iOS Simulator.
ستظهر أمثلة الواجهات المشتركة نفسها الموجودة في إصدار Android.
هل SwipingCards مناسبة لتطبيقات Production؟
توفر المكتبة API واضحة ومجموعة من الخصائص التي تساعد على إنشاء واجهات حديثة، لكن يجب اختبارها قبل اعتمادها في تطبيق إنتاجي.
خصوصًا أن الإصدار المنشور حاليًا هو 0.1.0، ولذلك من الأفضل مراجعة حالة المشروع والتوافق مع إصدارات Kotlin وCompose Multiplatform المستخدمة لديك.
كما يجب اختبار الأداء، وسلوك اللمس، ودعم الوصول Accessibility، وتوافق الواجهة مع أحجام الشاشات المختلفة.
هل تعمل SwipingCards مع Flutter؟
لا تعمل هذه المكتبة مباشرة داخل Flutter، لأنها مكتوبة باستخدام Kotlin وCompose Multiplatform.
لكن يمكنك إنشاء تجربة مشابهة في Flutter باستخدام أدوات مثل:
GestureDetector
AnimationController
Transform
Stack
AnimatedBuilder
Tween
Matrix4وتستطيع باستخدام هذه الأدوات بناء مجموعة بطاقات قابلة للسحب والدوران وتغيير الترتيب.
ترخيص مكتبة SwipingCards
المشروع مفتوح المصدر تحت ترخيص:
MIT Licenseيسمح هذا الترخيص باستخدام الكود وتعديله وتوزيعه، بما في ذلك داخل المشاريع التجارية، مع الالتزام بالاحتفاظ بإشعار حقوق النشر والترخيص المطلوب.
الخلاصة
تعتبر مكتبة SwipingCards خيارًا عمليًا لإنشاء واجهات Jetpack Compose Swipe Cards داخل تطبيقات Android وiOS باستخدام Compose Multiplatform.
تجمع المكتبة بين سهولة الاستخدام وحركات Spring وتأثيرات 3D Rotation ودعم الاتجاهات الأربعة، بالإضافة إلى إمكانية تخصيص المحتوى بالكامل وإعادة تدوير البطاقات بدل حذفها.
كما أن دعمها لعدد مختلف من البطاقات وتصميمات متعددة يجعلها مناسبة لتطبيقات التعارف والمحافظ الإلكترونية وتصفح المنتجات والأفلام وغيرها من التطبيقات التي تعتمد على التفاعل المباشر مع البطاقات.
وإذا كنت تريد التعرف على تقنيات تصميم واجهات Compose المتحركة، فإن دراسة الكود المصدري للمكتبة والأمثلة الموجودة داخل المستودع ستساعدك على فهم كيفية التعامل مع Gestures وAnimations وإدارة ترتيب العناصر.
الكود المصدري الكامل لمكتبة SwipingCards على GitHub
التوثيق الرسمي للتعامل مع Drag وSwipe وFling في Jetpack Compose
مزيد من المقالات : دمج Android Flavors مع Flutter: خطوات سهلة لربط تطبيقاتك





