diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ef75335..ce614bb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.RecyclerView" - tools:targetApi="31"> + tools:targetApi="34"> diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt new file mode 100644 index 0000000..5d25cae --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt @@ -0,0 +1,54 @@ +package otus.gpb.recyclerview + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import java.util.Collections + +class ChatAdapter() : ListAdapter(DiffUtilItem()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.chat, parent, false) + return ChatViewHolder(view) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val item = currentList.getOrNull(position) + item?.let { + (holder as ChatViewHolder).bind(item) + } + } + + fun exchange(fromPosition: Int, toPosition: Int) { + val list = currentList.toMutableList() + if (fromPosition < toPosition) { + for (index in fromPosition until toPosition) { + Collections.swap(list, index, index + 1) + } + } else { + for (index in fromPosition downTo toPosition + 1) { + Collections.swap(list, index, index - 1) + } + } + submitList(list) + } + + fun removeItem(fromPosition: Int) { + val list = currentList.toMutableList() + list.removeAt(fromPosition) + submitList(list) + } +} + +class DiffUtilItem : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean { + return oldItem == newItem + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatItem.kt b/app/src/main/java/otus/gpb/recyclerview/ChatItem.kt new file mode 100644 index 0000000..4fd1e9f --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatItem.kt @@ -0,0 +1,10 @@ +package otus.gpb.recyclerview + +data class ChatItem( + val id: Int, + val date: String, + val name: String, + val surname: String, + val message: String, + val background: Int +) \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt b/app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt new file mode 100644 index 0000000..3f62c4d --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt @@ -0,0 +1,28 @@ +package otus.gpb.recyclerview + +import android.view.View +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView + +class ChatViewHolder( + private val view: View, +) : RecyclerView.ViewHolder(view) { + + init { + println(this) + } + + private val name: TextView by lazy { view.findViewById(R.id.name) } + private val avatar: TextView by lazy { view.findViewById(R.id.avatar) } + private val message: TextView by lazy { view.findViewById(R.id.message) } + private val date: TextView by lazy { view.findViewById(R.id.date) } + + fun bind(item: ChatItem) { + println("bind item ${item.id}") + name.text = "${item.name} ${item.surname}" + message.text = item.message + date.text = item.date + avatar.text = item.name.substring(0, 1) + avatar.setBackgroundColor(item.background) + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ColorGenerator.kt b/app/src/main/java/otus/gpb/recyclerview/ColorGenerator.kt new file mode 100644 index 0000000..0d9ce40 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ColorGenerator.kt @@ -0,0 +1,13 @@ +package otus.gpb.recyclerview + +import android.graphics.Color +import java.util.Random + +object ColorGenerator { + + fun generateColor(): Int { + val nextInt = Random().nextInt(0xffffff + 1) + val colorCode = String.format("#%06x", nextInt) + return Color.parseColor(colorCode) + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/CustomDecorator.kt b/app/src/main/java/otus/gpb/recyclerview/CustomDecorator.kt new file mode 100644 index 0000000..ffbdc9a --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/CustomDecorator.kt @@ -0,0 +1,40 @@ +package otus.gpb.recyclerview + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ItemDecoration + +class CustomDecorator : ItemDecoration() { + + private val bounds = Rect() + private val paint = Paint().apply { + color = Color.GRAY + strokeWidth = 1f + } + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + + val childCount = parent.childCount + for (index in 0 until childCount) { + val child = parent.getChildAt(index) + parent.getDecoratedBoundsWithMargins(child, bounds) + + val positionCurrent = parent.getChildAdapterPosition(child) + if (positionCurrent != RecyclerView.NO_POSITION) { + val lastElementPosition = parent.adapter?.itemCount?.minus(1) + if (positionCurrent != lastElementPosition) { + canvas.drawLine( + bounds.left.toFloat() + 150f, + bounds.bottom.toFloat(), + bounds.right.toFloat(), + bounds.bottom.toFloat(), + paint + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ItemTouchHelper.kt b/app/src/main/java/otus/gpb/recyclerview/ItemTouchHelper.kt new file mode 100644 index 0000000..9f6f2e5 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ItemTouchHelper.kt @@ -0,0 +1,37 @@ +package otus.gpb.recyclerview + +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView + +class ItemTouchCallback : ItemTouchHelper.Callback() { + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + return makeMovementFlags( + ItemTouchHelper.UP or ItemTouchHelper.DOWN, + ItemTouchHelper.LEFT + ) + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + viewHolder.chatAdapter.exchange( + viewHolder.bindingAdapterPosition, + target.bindingAdapterPosition + ) + return true + } + + override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float { + return 0.25f + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + viewHolder.chatAdapter.removeItem(viewHolder.absoluteAdapterPosition) + } + + private val RecyclerView.ViewHolder.chatAdapter: ChatAdapter + get() = bindingAdapter as? ChatAdapter ?: error("Not ChatAdapter") +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index e2cdca7..aba0ecf 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -1,12 +1,76 @@ package otus.gpb.recyclerview -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.widget.Toast +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.floatingactionbutton.FloatingActionButton class MainActivity : AppCompatActivity() { + private val adapter: ChatAdapter by lazy { ChatAdapter() } + private var list: List = emptyList() + private var chartIdx: Int = 0 + private val maxChartIdx: Int = 30 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() setContentView(R.layout.activity_main) + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets + } + + val recyclerView = findViewById(R.id.list) + recyclerView.addItemDecoration( + CustomDecorator() + ) + + ItemTouchHelper(ItemTouchCallback()).attachToRecyclerView(recyclerView) + + recyclerView.adapter = adapter + list = loadList(10) + adapter.submitList(list) + + findViewById(R.id.loadMore).setOnClickListener { addChats() } + } + + private fun addChats() { + //val currentList = list.toMutableList() + val currentList = adapter.currentList.toMutableList() + currentList.addAll(loadList(10)) + list = currentList + adapter.submitList(list) + } + + private fun loadList(count: Int) = run { + val list = mutableListOf() + if (chartIdx > maxChartIdx) { + val text = "Все данные подгружены" + val duration = Toast.LENGTH_SHORT + val toast = Toast.makeText(applicationContext, text, duration) + toast.show() + list.toList() + } else { + for(n in chartIdx + 1..chartIdx + count){ + val personItem = ChatItem( + id = n, + name = "Name_$n", + surname = "Surname_$n", + message = "Messages from Name_$n", + date = "00:00", + background = ColorGenerator.generateColor() + ) + list.add(personItem) + } + chartIdx += count + list.toList() + } } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2d026df..90e0a72 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,13 +1,34 @@ - + android:layout_height="match_parent" + android:orientation="vertical" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:itemCount="5" + tools:listitem="@layout/chat" /> - \ No newline at end of file + + diff --git a/app/src/main/res/layout/chat.xml b/app/src/main/res/layout/chat.xml new file mode 100644 index 0000000..8940d83 --- /dev/null +++ b/app/src/main/res/layout/chat.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3d78b1f..d202084 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ RecyclerView + Load more \ No newline at end of file