ورود / ثبت نام سبد خرید 0
تبلیغات : محصولاتی که ممکن است خوشتان بیاید
در این مقاله ی آموزشی قصد داریم به شما یاد بدیم چطور روی آیتم های یک recyclerview عمل swipe به چپ یا راست را انجام دهید و بعد از swipe کردن کاربر آنرا حذف کنید . درست مثل اپلیکیشن اندرویدی GMail .
در این مقاله یاد میگیرید چطور یک آیتم را هنگام swipe پاک کنید و بعد از پاکسازی Undo هم برای لغو حذف نمایش دهید .
از نظر UX برای کاربران اپلیکیشن ها ، swipe کردن روی آیتم ها برای حذف کردن آنها بسیار راحت و دلچسب تر خواهد بود .
شما میتوانید از این آموزش برای تغییرات شخصی خودتان نیز به سادگی استفاده کنید .
ابتدا نتیجه ی نهایی این آموزش را بصورت ویدیویی در زیر مشاهده کنیم :
خب بیایید شروع کنیم
قدم 1 - پروژه جدید کاتلین بسازید :
در محیط اندروید استودیو خودتان یک پروژه جدید بسازید و یک اکتیویتی empty جدیدی بسازید . و یادتون نره موقع ساختن پروژه کاتلین رو بعنوان زبان برنامه نویسی انتخاب کنید .
حالا فایل build.gradle(Module: app) را باز کنید و کدهای زیر را بهش اضافه کنید
implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.android.support:design:28.0.0'
البته اگر قصد دارید از کتابخانه جدیدتر اینها استفاده کنید از AndroidX استفاده کنید که نحوه ی افزودن کتابخانه ها با androidx را به راحتی میتونید گوگل کنید .
خب برای اضافه کردن recyclerview و cardview و material desing به پروژمون ما نیاز داریم سه کتابخانه بالا را به گردل اضافه کنیم بعد اتصال به اینترنت با دور زدن ip ایران بخاطر تحریم ها نیاز است و سپس sync کنید تا کتابخانه های بالا دانلود و به پروژتون اضافه بشن .
قدم 2 - اضافه کردن تصویر حذف :
خب یک فایل png تصویری پیدا کنید که تصویر سطل آشغال باشه برای پیدا کردن چنین تصویری مستقیما میتوانید در گوگل عبارت زیر را کپی و سرچ کنید ( البته بخش تصاویر ) :
trash icon filetype:png
و بعد از پیدا کردن تصویر آنرا در مسیر app->res->drawable کپی کنید .
قدم 3 - اضافه کردن فایل XML و کلاس مدل :
خب در مسیر app->res->layout یک فایل xml جدید بسازید و نام آنرا rv_item.xml قرار دهید .
فایل rv_item.xml باید محتوای زیر را داشته باشد :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="5dp" android:paddingLeft="5dp" android:paddingRight="5dp" tools:context=".MainActivity" tools:ignore="NamespaceTypo"> <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="90dp" android:layout_gravity="center" app:cardBackgroundColor="#ffffff" android:layout_marginTop="0dp" card_view:cardCornerRadius="1dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/tv" android:height="90dp" android:gravity="center" android:paddingLeft="10dp" android:text="Image" android:textColor="#000" android:textStyle="bold" android:textSize="18sp" /> </LinearLayout> </android.support.v7.widget.CardView> </RelativeLayout>
خب همانطور که در کدهای بالا میبینید این کدها یک textview را داخل یک cardview میسازد پس هر ردیف recyclerview یک عدد cardview خواهد داشت که درونش یک textview وجود دارد .
حالا یک فایل کاتلین جدید بسازید و اسمش رو SwipeModel.kt بزارید .
محتویات SwipeModel.kt باید به شکل زیر باشه :
class Model { var name: String? = null fun getNames(): String { return name.toString() } fun setNames(name: String) { this.name = name } }
در کدهای بالا یک متغیر از نوع رشته ای یا string تعریف کردیم که قراره نام ماشین را نگهداری بکنه و روی recyclerview به نمایش بذاره
برای این متغیر دو متد کمک کننده ی Setter و Getter نوشتیم که دریافت و ارسال اطلاعات را راحت میکنه .
قدم 4 - آداپتر برای swipe :
یک فایل کلاس از نوع کاتلین بسازید و اسم انرا SwipeAdapter.kt انتخاب کنید .
محتویات SwipeAdapter.kt باید به شکل زیر باشد :
import android.content.Context import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import java.util.ArrayList /** * Created by avasam.ir in 2019 */ class SwipeAdapter(ctx: Context, private val imageModelArrayList: ArrayList<Model>) : RecyclerView.Adapter<SwipeAdapter.MyViewHolder>() { private val inflater: LayoutInflater init { inflater = LayoutInflater.from(ctx) } fun removeItem(position: Int) { imageModelArrayList.removeAt(position) notifyItemRemoved(position) notifyItemRangeChanged(position, imageModelArrayList.size) } fun restoreItem(model: Model, position: Int) { imageModelArrayList.add(position, model) // notify item added by position notifyItemInserted(position) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val view = inflater.inflate(R.layout.rv_item, parent, false) return MyViewHolder(view) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.time.setText(imageModelArrayList[position].getNames()) } override fun getItemCount(): Int { return imageModelArrayList.size } inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var time: TextView init { time = itemView.findViewById(R.id.tv) as TextView } } }
در تابع onCreateViewHolder() ما rv_item.xml را پر میکنیم تا ویو مربوط به هر ردیف ساخته شود .
سپس در تابع onBindViewHolder() نام ماشین را در textview ست میکنیم ، برای این از imageModelArrayList بعنوان منبع دیتا استفاده کرده ایم .
imageModelArrayList یک arraylist است که درونش از آبجکت های SwipeModel پر میشود که آداپتر آنرا از پارامترها میگیرد .
خب حالا به کد زیر نگاه کنید :
fun removeItem(position: Int) { imageModelArrayList.removeAt(position) notifyItemRemoved(position) notifyItemRangeChanged(position, imageModelArrayList.size) }
این تابع زمانی اجرا میشود که کاربر به سمت راست یا چپ عمل swipe را انجام دهد و سپس دکمه ی undo را نزند .
کامپایلر آیتمی که روی آن عمل swipe اتفاق افتاده است را پاکسازی خواهد کرد .
سپس متد notifyItemRemoved() فراخوانی میشود و سپس متد notifyItemRangeChanged() ریسایکلر ویو را ریفرش میکند .
حالا به تابع زیر نگاه کنید :
fun restoreItem(model: Model, position: Int) { imageModelArrayList.add(position, model) // notify item added by position notifyItemInserted(position) }
اگر کاربر دکمه ی undo را کلیک کند قبل از اینکه آیتم حذف شود کامپایلر متد زیر را فراخوانی میکند .
ابتدا آیتم حذف شده را دوباره به سرجایش برمیگرداند و سپس متد notifyItemInserted() را فراخوانی میکند تا ریسایکلر ویو ریفرش شود .
قدم 5 - تغییرات اکتیویتی اصلی برنامه :
خب ما دو فایل در اکتیویتی اصلی داریم یکی فایل کاتلین و یکی فایل لیوت آن بنام activity_main.xml که کدش در زیر است :
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/linear" android:background="#e4e0e0" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="10dp"/> </android.support.design.widget.CoordinatorLayout>
خب همانطور که میبینید چیز خاصی ندارد و فقط یک recyclerview به آن اضافه شده است
کد های زیر هم برای MainActivity.kt است :
import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Color import android.graphics.Paint import android.graphics.RectF import android.support.design.widget.Snackbar import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.graphics.Canvas import java.util.ArrayList import android.support.v7.widget.helper.ItemTouchHelper class MainActivity : AppCompatActivity() { private var recyclerView: RecyclerView? = null private var imageModelArrayList: ArrayList<Model>? = null private var adapter: SwipeAdapter? = null private val p = Paint() private val myImageNameList = arrayOf("Benz", "Bike", "Car", "Carrera", "Ferrari", "Harly", "Lamborghini", "Silver") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) recyclerView = findViewById(R.id.recycler) as RecyclerView imageModelArrayList = populateList() adapter = SwipeAdapter(this, imageModelArrayList!!) recyclerView!!.adapter = adapter recyclerView!!.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false) enableSwipe() } private fun enableSwipe() { val simpleItemTouchCallback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { return false } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.adapterPosition if (direction == ItemTouchHelper.LEFT) { val deletedModel = imageModelArrayList!![position] adapter!!.removeItem(position) // showing snack bar with Undo option val snackbar = Snackbar.make( window.decorView.rootView, " removed from Recyclerview!", Snackbar.LENGTH_LONG ) snackbar.setAction("UNDO") { // undo is selected, restore the deleted item adapter!!.restoreItem(deletedModel, position) } snackbar.setActionTextColor(Color.YELLOW) snackbar.show() } else { val deletedModel = imageModelArrayList!![position] adapter!!.removeItem(position) // showing snack bar with Undo option val snackbar = Snackbar.make( window.decorView.rootView, " removed from Recyclerview!", Snackbar.LENGTH_LONG ) snackbar.setAction("UNDO") { // undo is selected, restore the deleted item adapter!!.restoreItem(deletedModel, position) } snackbar.setActionTextColor(Color.YELLOW) snackbar.show() } } override fun onChildDraw( c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean ) { val icon: Bitmap if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { val itemView = viewHolder.itemView val height = itemView.bottom.toFloat() - itemView.top.toFloat() val width = height / 3 if (dX > 0) { p.color = Color.parseColor("#388E3C") val background = RectF(itemView.left.toFloat(), itemView.top.toFloat(), dX, itemView.bottom.toFloat()) c.drawRect(background, p) icon = BitmapFactory.decodeResource(resources, R.drawable.delete) val icon_dest = RectF( itemView.left.toFloat() + width, itemView.top.toFloat() + width, itemView.left.toFloat() + 2 * width, itemView.bottom.toFloat() - width ) c.drawBitmap(icon, null, icon_dest, p) } else { p.color = Color.parseColor("#D32F2F") val background = RectF( itemView.right.toFloat() + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat() ) c.drawRect(background, p) icon = BitmapFactory.decodeResource(resources, R.drawable.delete) val icon_dest = RectF( itemView.right.toFloat() - 2 * width, itemView.top.toFloat() + width, itemView.right.toFloat() - width, itemView.bottom.toFloat() - width ) c.drawBitmap(icon, null, icon_dest, p) } } super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) } } val itemTouchHelper = ItemTouchHelper(simpleItemTouchCallback) itemTouchHelper.attachToRecyclerView(recyclerView) } private fun populateList(): ArrayList<Model> { val list = ArrayList<Model>() for (i in 0..7) { val imageModel = Model() imageModel.setNames(myImageNameList[i]) list.add(imageModel) } return list } }
خب بیایید اکتیویتی اصلی را بررسی کنیم .
ابتدا کدهای زیر را میبینید :
private var recyclerView: RecyclerView? = null private var imageModelArrayList: ArrayList<Model>? = null private var adapter: SwipeAdapter? = null private val p = Paint() private val myImageNameList = arrayOf("Benz", "Bike", "Car", "Carre
خب در ابتدا یک ابجکت از Recyclerview ساختیم و سپس یک SwipeAdapter و بعد هم کلاس های مورد نیازمان
یک آرایه با ابجکت های مدلی که بالاتر ساختیم و اسمش imageModelArrayList است
و یک آرایه ی از نوع string دیگر که نام وسایل نقلیه را نگهداری میکند مانند Benz Bike Car و ...
حالا کدهای زیر را ببینید :
imageModelArrayList = populateList() adapter = SwipeAdapter(this, imageModelArrayList!!) recyclerView!!.adapter = adapter recyclerView!!.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false) enableSwipe()
کامپایلر populateList() را استفاده خواهد کرد که دیتا را داخل imageModelArrayList قرار دهد .
سپس adapter را به recyclerview معرفی و bind خواهد کرد .
کامپایلر متد enableSwipe() را صدا میزند .
در زیر کدهای متد enableSwipe() میبینید :
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.adapterPosition if (direction == ItemTouchHelper.LEFT) { val deletedModel = imageModelArrayList!![position] adapter!!.removeItem(position) // showing snack bar with Undo option val snackbar = Snackbar.make( window.decorView.rootView, " removed from Recyclerview!", Snackbar.LENGTH_LONG ) snackbar.setAction("UNDO") { // undo is selected, restore the deleted item adapter!!.restoreItem(deletedModel, position) } snackbar.setActionTextColor(Color.YELLOW) snackbar.show() } else { val deletedModel = imageModelArrayList!![position] adapter!!.removeItem(position) // showing snack bar with Undo option val snackbar = Snackbar.make( window.decorView.rootView, " removed from Recyclerview!", Snackbar.LENGTH_LONG ) snackbar.setAction("UNDO") { // undo is selected, restore the deleted item adapter!!.restoreItem(deletedModel, position) } snackbar.setActionTextColor(Color.YELLOW) snackbar.show() } }
کامپایلر متد onSwiped() را زمانی صدا میزند که کاربر به سمت راست یا چپ swipe بکند .
سپس یک شرط if گذاشتیم تا بررسی کند عمل swipe به کدام سمت بوده است .
سپس کامپایلر آبجکتی که میخواهد پاک شود را انتخاب میکند .
سپس متد removeItem() را صدا میزند که درون کلاس آداپتر است
سپس یک snack bar نمایش داده میشود .
زمانی که کاربر دکمه ی UNDO را روی snack bar کلیک میکند کامپایلر متد restoreItem() را صدا خواهد زد .
و کار ما با پیاده سازی این سیستم تمام شد .
مطالب زیر ممکن است برای شما مفید باشد
محصولات برگزیده مناسب شما