diff --git a/.gitignore b/.gitignore index 39fb081..4b3d872 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ *.iml .gradle /local.properties -/.idea/workspace.xml -/.idea/libraries .DS_Store /build /captures .externalNativeBuild + +/.idea/ +build/ diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 6c06580..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/dictionaries/AlexZandR.xml b/.idea/dictionaries/AlexZandR.xml deleted file mode 100644 index 7b513bd..0000000 --- a/.idea/dictionaries/AlexZandR.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 97626ba..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 7ac24c7..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 3cdb271..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - Java - - - - - - - - - - - - - - - - - - - - - - $USER_HOME$/.subversion - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 5f95fd3..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/LICENSE.MD b/LICENSE.MD new file mode 100644 index 0000000..031d603 --- /dev/null +++ b/LICENSE.MD @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 [Agilie Team](https://agilie.com/en/index) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1cd2508 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# SwipeToDelete + +[ ![Download](https://api.bintray.com/packages/agilie/maven/Swipe2Delete/images/download.svg) ](https://bintray.com/agilie/maven/Swipe2Delete/_latestVersion) + + + +# What is SwipeToDelete? + +SwipeToDelete is a library you can use to simplify implementation of well-known (I believe for every Android developer :) ) swipe-to-delete behavior for your data lists. The main idea is that user has some time to undo unwanted *delete* operation. Swipe twice to remove the row immediately! Also, this library is fully customizable, feel free to use your own UI for *undo* layer, add extra buttons etc. + +# Features +* Based on interface implementation; +* Supports pending deletion with bottom layout; +* Full customization of upper layout and bottom layout; +* Easy way to handle deletion. All you need is to call **removeItem(key: K)** which should be overriden in your adapter and to invoke **swipeToDeleteDelegate.removeItem(key: K)** at the end of method's definition. + +# Usage + +### Gradle + +Add dependency in your `build.gradle` file: +````gradle +compile 'com.agilie:swipe2delete:1.0' +```` + +### Maven +Add rependency in your `.pom` file: +````xml + + com.agilie + swipe2delete + 1.0 + pom + +```` + +### How does it work? + +* Implement **ISwipeToDeleteAdapter** in your own adapter or another class. +* Make instance of **SwipeToDeleteDelegate** in your own adapter + +```kotlin +SwipeToDeleteDelegate(context = context, items = mutableList, swipeToDeleteDelegate = this) +``` + +* Call corresponding methods in your overrided methods + +```kotlin + override fun onBindViewHolder(holder: Holder, position: Int) { + swipeToDeleteDelegate.onBindViewHolder(holder, mutableList[position].name, position) + } + + override fun removeItem(key: String) { + swipeToDeleteDelegate.removeItem(key) + } +``` + + +* Implement **ISwipeToDeleteHolder** in your holder. You need to have container with your regular item layout and if you need bottom container too. +* In your holder you need to have **var pendingDelete** as false by default and you need to override **val topContainer** + +```kotlin +override val topContainer: View + get() = + if (pendingDelete) undoContainer + else itemContainer + +override var pendingDelete: Boolean = false +``` + + +* Also you need to make default key in holder by yourself + +```kotlin +override var key: Int = -1 +``` + + +* If you need bottom container appearence while waiting, simply override **onBindPendingItem(holder: Holder, key: Int, item: User)** method: + +```kotlin +override fun onBindPendingItem(holder: Holder, key: Int, item: User) { +... +} +``` +* Also you can implement **IAnimationUpdateListener** and **IAnimatorListener** to override methods to achieve animation along the axis x with duration which equally deleting duration + +``` +UserAdapter(...) : ... , IAnimationUpdateListener { +... + fun onAnimationUpdated(animation: android.animation.ValueAnimator?, options: ModelOptions<*>) {} + + fun onAnimationEnd(animation: Animator?, options: ModelOptions<*>) {} + + fun onAnimationCancel(animation: Animator?, options: ModelOptions<*>) {} + + fun onAnimationStart(animation: Animator?, options: ModelOptions<*>) {} + + fun onAnimationRepeat(animation: Animator, options: ModelOptions<*>) {} +} +``` + +To get more information refer our [usage example](app/). Just clone the project and run this module. Examples are provided in [Java](app/src/main/java/agilie/example/swipe2delete/java/) and [Kotlin](app/src/main/java/agilie/example/swipe2delete/kotlin/) as well! + +## Troubleshooting +Problems? Check the [Issues](https://github.com/agilie/SwipeToDelete/issues) block +to find the solution or create an new issue that we will fix asap. Feel free to contribute. + +## Author + +This library is open-sourced by [Agilie Team](https://www.agilie.com) + +## Contributors + +- [Alexander Nekrasov](https://github.com/TermLog) +- [Roman Kapshuk](https://github.com/RomanKapshuk) + +## Contact us +If you have any questions, suggestions or just need a help with web or mobile development, please email us at

+You can ask us anything from basic to complex questions.
+We will continue publishing new open-source projects. Stay with us, more updates will follow!
+ +## License + +The [MIT](LICENSE.MD) License (MIT) Copyright © 2017 [Agilie Team](https://www.agilie.com) + diff --git a/app/.gitignore b/app/.gitignore index 796b96d..42afabf 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1 @@ -/build +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 558e1ac..461d16f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 25 @@ -20,12 +22,19 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) + +// compile project(':swipe2delete') + compile 'com.agilie:swipe2delete:1.0' compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support:design:25.3.1' testCompile 'junit:junit:4.12' + compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" +} +repositories { + mavenCentral() } diff --git a/app/src/androidTest/java/test/alexzander/swipetodelete/ExampleInstrumentedTest.java b/app/src/androidTest/java/test/alexzander/swipetodelete/ExampleInstrumentedTest.java index 7860355..21d6dbb 100644 --- a/app/src/androidTest/java/test/alexzander/swipetodelete/ExampleInstrumentedTest.java +++ b/app/src/androidTest/java/test/alexzander/swipetodelete/ExampleInstrumentedTest.java @@ -7,7 +7,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * Instrumentation test, which will execute on an Android device. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9f57f6d..b694e03 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,24 +1,27 @@ + package="test.alexzander.swipetodelete"> + android:theme="@style/AppTheme"> + > - + - + + + \ No newline at end of file diff --git a/app/src/main/java/agilie/example/swipe2delete/User.kt b/app/src/main/java/agilie/example/swipe2delete/User.kt new file mode 100644 index 0000000..914513f --- /dev/null +++ b/app/src/main/java/agilie/example/swipe2delete/User.kt @@ -0,0 +1,4 @@ +package agilie.example.swipe2delete + + +data class User(val id: Int, val name: String, val country: String = "") \ No newline at end of file diff --git a/app/src/main/java/agilie/example/swipe2delete/Utility.kt b/app/src/main/java/agilie/example/swipe2delete/Utility.kt new file mode 100644 index 0000000..17feab9 --- /dev/null +++ b/app/src/main/java/agilie/example/swipe2delete/Utility.kt @@ -0,0 +1,37 @@ +package agilie.example.swipe2delete + +fun prepareContactList(count: Int): MutableList { + val result = ArrayList(count) + result.add(User(0, "Daniel Weaver", "Nauru")) + result.add(User(1, "Vance Stanton", "Russian Federation")) + result.add(User(2, "Nasim Ingram", "United Arab Emirates")) + result.add(User(3, "Duncan Grimes", "Libya")) + result.add(User(4, "Logan Mcgee","Palau")) + result.add(User(5, "Berk Kemp", "Albania")) + result.add(User(6, "Solomon Warren", "Viet Nam")) + result.add(User(7, "Quentin Maldonado", "Aruba")) + result.add(User(8, "Isaac Garza", "Malta")) + result.add(User(9, "Tobias Workman", "Mali")) + result.add(User(10, "Channing Frederick", "Congo (Brazzaville)")) + result.add(User(11, "Phelan Booker", "Cocos (Keeling) Islands")) + result.add(User(12, "Amery Lewis", "San Marino")) + result.add(User(13, "Valentine Nolan", "Turks and Caicos Islands")) + result.add(User(14, "Jamal Walls", "Christmas Island")) + result.add(User(15, "Jack Noel", "Nicaragua")) + result.add(User(16, "Alvin Peterson", "Malta")) + result.add(User(17, "Brody Gilmore", "Bonaire, Sint Eustatius and Saba")) + result.add(User(18, "Arden Rich", "Uzbekistan")) + result.add(User(19, "Andrew Bauer", "Indonesia")) + result.add(User(20, "Erich Bernard", "India")) + result.add(User(21, "George Hayes", "Georgia")) + result.add(User(22, "Carlos Slater", "France")) + result.add(User(23, "Talon Harrington", "Congo (Brazzaville)")) + result.add(User(24, "Silas Navarro", "French Southern Territories")) + result.add(User(25, "Ryan Drake", "Maldives")) + result.add(User(26, "Jesse Rojas", "Sao Tome and Principe")) + result.add(User(27, "Aladdin Blankenship", "Tunisia")) + result.add(User(28, "Raja Valdez", "Holy See (Vatican City State)")) + result.add(User(29, "Chadwick Watkins", "Dominican Republic")) + result.add(User(30, "Chaney Best", "French Polynesia")) + return result +} \ No newline at end of file diff --git a/app/src/main/java/agilie/example/swipe2delete/java/UserAdapter.java b/app/src/main/java/agilie/example/swipe2delete/java/UserAdapter.java new file mode 100644 index 0000000..671be4e --- /dev/null +++ b/app/src/main/java/agilie/example/swipe2delete/java/UserAdapter.java @@ -0,0 +1,152 @@ +package agilie.example.swipe2delete.java; + +import android.support.v7.widget.AppCompatTextView; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.FrameLayout; + +import com.agilie.swipe2delete.SwipeToDeleteDelegate; +import com.agilie.swipe2delete.interfaces.ISwipeToDeleteAdapter; +import com.agilie.swipe2delete.interfaces.ISwipeToDeleteHolder; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import agilie.example.swipe2delete.User; +import test.alexzander.swipetodelete.R; + + +public class UserAdapter extends RecyclerView.Adapter implements ISwipeToDeleteAdapter { + + private List users; + private SwipeToDeleteDelegate swipeToDeleteDelegate; + + public UserAdapter(List users) { + this.users = users; + } + + public List getUsers() { + return users; + } + + public SwipeToDeleteDelegate getSwipeToDeleteDelegate() { + return swipeToDeleteDelegate; + } + + public void setSwipeToDeleteDelegate(SwipeToDeleteDelegate swipeToDeleteDelegate) { + this.swipeToDeleteDelegate = swipeToDeleteDelegate; + swipeToDeleteDelegate.setDeletingDuration(5000L); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false); + return new Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder instanceof ISwipeToDeleteHolder) { + swipeToDeleteDelegate.onBindViewHolder((ISwipeToDeleteHolder) holder, users.get(position).getName(), position); + } + } + + @Override + public int getItemCount() { + return users.size(); + } + + @Override + public int findItemPositionByKey(String key) { + for (int i = 0; i < users.size(); ++i) { + if (users.get(i).getName().equals(key)) { + return i; + } + } + return -1; + } + + @Override + public void onBindCommonItem(Holder holder, final String key, User item, int position) { + holder.userContainer.setVisibility(View.VISIBLE); + holder.undoContainer.setVisibility(View.GONE); + holder.userName.setText(item.getName()); + } + + @Override + public void onBindPendingItem(Holder holder, final String key, User item, int position) { + holder.undoContainer.setVisibility(View.VISIBLE); + holder.userContainer.setVisibility(View.GONE); + holder.userButtonUndo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + swipeToDeleteDelegate.onUndo(key); + } + }); + } + + @Override + public void removeItem(String key) { + swipeToDeleteDelegate.removeItem(key); + } + + @Override + public void onItemDeleted(User item) { + + } + + public class Holder extends RecyclerView.ViewHolder implements ISwipeToDeleteHolder { + + Button userButtonUndo; + FrameLayout userContainer; + + AppCompatTextView userName; + FrameLayout undoContainer; + + boolean pendingDelete; + String key; + + Holder(View view) { + super(view); + userName = (AppCompatTextView) view.findViewById(R.id.user_name); + userContainer = (FrameLayout) view.findViewById(R.id.user_container); + + userButtonUndo = (Button) view.findViewById(R.id.user_button_undo); + undoContainer = (FrameLayout) view.findViewById(R.id.user_undo_container); + } + + @Override + public boolean getPendingDelete() { + return pendingDelete; + } + + @Override + public void setPendingDelete(boolean b) { + pendingDelete = b; + } + + @NotNull + @Override + public View getTopContainer() { + if (pendingDelete) { + return undoContainer; + } else { + return userContainer; + } + } + + @Override + public String getKey() { + return key; + } + + @Override + public void setKey(String s) { + key = s; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/agilie/example/swipe2delete/kotlin/UserAdapter.kt b/app/src/main/java/agilie/example/swipe2delete/kotlin/UserAdapter.kt new file mode 100644 index 0000000..ea41ae2 --- /dev/null +++ b/app/src/main/java/agilie/example/swipe2delete/kotlin/UserAdapter.kt @@ -0,0 +1,114 @@ +package agilie.example.swipe2delete.kotlin + +import agilie.example.swipe2delete.User +import android.animation.Animator +import android.animation.ValueAnimator +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.agilie.swipe2delete.ModelOptions +import com.agilie.swipe2delete.SwipeToDeleteDelegate +import com.agilie.swipe2delete.interfaces.IAnimationUpdateListener +import com.agilie.swipe2delete.interfaces.IAnimatorListener +import com.agilie.swipe2delete.interfaces.ISwipeToDeleteAdapter +import com.agilie.swipe2delete.interfaces.ISwipeToDeleteHolder +import kotlinx.android.synthetic.main.activity_main_item.view.* +import test.alexzander.swipetodelete.R.layout.activity_main_item + + +class UserAdapter(val mutableList: MutableList, var onItemClick: (User) -> Any) : + RecyclerView.Adapter(), ISwipeToDeleteAdapter, IAnimationUpdateListener, IAnimatorListener { + + val swipeToDeleteDelegate = SwipeToDeleteDelegate(items = mutableList, swipeToDeleteAdapter = this) + + var animationEnabled = true + var bottomContainer = true + + init { + swipeToDeleteDelegate.deletingDuration = 3000 + } + + override fun getItemCount() = mutableList.size + + override fun onBindViewHolder(holder: MyHolder, position: Int) { + swipeToDeleteDelegate.onBindViewHolder(holder, mutableList[position].id, position) + } + + override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int) = + MyHolder(LayoutInflater.from(parent?.context).inflate(activity_main_item, parent, false)) + + override fun removeItem(key: Int) { + swipeToDeleteDelegate.removeItem(key) + } + + override fun onAnimationEnd(animation: Animator?, options: ModelOptions<*>) { + if (animationEnabled) { + swipeToDeleteDelegate.holders[options.key]?.progressBar?.animate()?.x(0.0f)?.withEndAction { + swipeToDeleteDelegate.holders[options.key]?.progressBar?.visibility = View.GONE + } + } + } + + override fun onAnimationUpdated(animation: ValueAnimator?, options: ModelOptions<*>) { + if (animationEnabled) { + val posX = animation?.animatedValue as Float + swipeToDeleteDelegate.holders[options.key]?.progressBar?.x = posX + options.posX = posX + } + } + + override fun onAnimationStart(animation: Animator?, options: ModelOptions<*>) { + if (animationEnabled) { + swipeToDeleteDelegate.holders[options.key]?.progressBar?.visibility = View.VISIBLE + } + } + + override fun findItemPositionByKey(key: Int) = (0..mutableList.lastIndex).firstOrNull { mutableList[it].id == key } ?: -1 + + override fun onBindCommonItem(holder: MyHolder, key: Int, item: User, position: Int) { + holder.apply { + name.text = item.name + id.text = item.country + itemContainer.visibility = View.VISIBLE + undoData.visibility = View.GONE + progressBar.visibility = View.GONE + itemContainer.setOnClickListener { onItemClick.invoke(item) } + } + } + + override fun onBindPendingItem(holder: MyHolder, key: Int, item: User, position: Int) { + holder.itemContainer.visibility = View.GONE + if (bottomContainer) { + holder.apply { + deletedName.text = "You have just deleted ${item.name}" + itemContainer.visibility = View.GONE + undoData.visibility = View.VISIBLE + if (animationEnabled) { + progressBar.visibility = View.VISIBLE + } + undoButton.setOnClickListener { swipeToDeleteDelegate.onUndo(key) } + } + + } + } + + inner class MyHolder(view: View) : RecyclerView.ViewHolder(view), ISwipeToDeleteHolder { + + var deletedName = view.user_name_deleted + var name = view.user_name + var id = view.user_id + var itemContainer = view.contact_container + var undoContainer = view.undo_container + var undoData = view.undo_data + var undoButton = view.button_undo + var progressBar = view.progress_indicator + + override val topContainer: View + get() = + if (pendingDelete && bottomContainer) undoContainer + else itemContainer + override var key: Int = -1 + override var pendingDelete: Boolean = false + } +} \ No newline at end of file diff --git a/app/src/main/java/agilie/example/swipe2delete/kotlin/UsersActivity.kt b/app/src/main/java/agilie/example/swipe2delete/kotlin/UsersActivity.kt new file mode 100644 index 0000000..b907676 --- /dev/null +++ b/app/src/main/java/agilie/example/swipe2delete/kotlin/UsersActivity.kt @@ -0,0 +1,66 @@ +package agilie.example.swipe2delete.kotlin + +import agilie.example.swipe2delete.prepareContactList +import android.support.v7.widget.DividerItemDecoration +import android.support.v7.widget.helper.ItemTouchHelper +import android.view.Menu +import android.view.MenuItem +import android.widget.LinearLayout.VERTICAL +import android.widget.Toast +import kotlinx.android.synthetic.main.activity_java.* +import kotlinx.android.synthetic.main.recycler_view.* +import test.alexzander.swipetodelete.R +import test.alexzander.swipetodelete.R.layout.activity_main + +class UsersActivity : android.support.v7.app.AppCompatActivity() { + + var adapter: UserAdapter? = null + + override fun onCreate(savedInstanceState: android.os.Bundle?) { + super.onCreate(savedInstanceState) + setContentView(activity_main) + setSupportActionBar(toolbar) + initRecyclerView() + } + + fun initRecyclerView() { + adapter = UserAdapter(prepareContactList(60)) { + user -> + Toast.makeText(this, user.toString(), Toast.LENGTH_SHORT).show() + } + + adapter?.swipeToDeleteDelegate?.pending = true + recyclerView.adapter = adapter + + val dividerItemDecoration = DividerItemDecoration(this, VERTICAL) + recyclerView.addItemDecoration(dividerItemDecoration) + + val itemTouchHelper = ItemTouchHelper(adapter?.swipeToDeleteDelegate?.itemTouchCallBack) + itemTouchHelper.attachToRecyclerView(recyclerView) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem?) = + when (item?.itemId) { + R.id.action_undo_animation -> { + item.isChecked = !item.isChecked + adapter?.animationEnabled = item.isChecked + true + } + R.id.action_bottom_container -> { + item.isChecked = !item.isChecked + adapter?.bottomContainer = item.isChecked + true + } + R.id.action_pending -> { + item.isChecked = !item.isChecked + adapter?.swipeToDeleteDelegate?.pending = item.isChecked + true + } + else -> super.onOptionsItemSelected(item) + } +} diff --git a/app/src/main/java/test/alexzander/swipetodelete/ContactAdapter.java b/app/src/main/java/test/alexzander/swipetodelete/ContactAdapter.java deleted file mode 100644 index bd73298..0000000 --- a/app/src/main/java/test/alexzander/swipetodelete/ContactAdapter.java +++ /dev/null @@ -1,139 +0,0 @@ -package test.alexzander.swipetodelete; - -import android.animation.ValueAnimator; -import android.os.Handler; -import android.os.Looper; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import java.util.HashMap; -import java.util.List; - -import static test.alexzander.swipetodelete.ContactAdapterUtils.PENDING_DURATION; - -/** - * Created by AlexZandR on 4/24/17 - */ - -public class ContactAdapter extends RecyclerView.Adapter implements ItemSwipeListener, - UndoClickListener { - - private Handler handler = new Handler(Looper.getMainLooper()); - private HashMap pendingRemoveActions = new HashMap<>(1); - private HashMap animatorsMap = new HashMap<>(1); - private List contacts; - - public ContactAdapter(List contacts) { - this.contacts = contacts; - } - - @Override - public ContactHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate( - R.layout.list_item, parent, false); - return new ContactHolder(view, this); - } - - @Override - public void onBindViewHolder(final ContactHolder holder, int position) { - try { - final ItemContact contact = contacts.get(position); - if (contact == null) { - contacts.remove(position); - notifyItemRemoved(position); - } else { - holder.isPendingDelete = contact.isPendingDelete; - if (contact.isPendingDelete) { - onBindPendingContact(holder, contact); - } else { - onBindCommonContact(holder, contact); - } - } - } catch (IndexOutOfBoundsException exc) { - exc.printStackTrace(); - } - } - - @Override - public int getItemCount() { - return contacts.size(); - } - - @Override - public void onItemSwiped(final ContactHolder holder, final int swipeDir) { - int position = holder.getAdapterPosition(); - final ItemContact contact = contacts.get(position); - if (contact.isPendingDelete) { - removeItem(contact); - } else { - contact.isPendingDelete = true; - contact.setDirection(swipeDir); - notifyItemChanged(position); - } - } - - @Override - public void onUndoClick(final int position) { - if (position != -1) { - final String mapKey = contacts.get(position).name; - handler.removeCallbacks(pendingRemoveActions.get(mapKey)); - contacts.get(position).isPendingDelete = false; - notifyItemChanged(position); - ContactAdapterUtils.clearAnimator(animatorsMap.get(mapKey)); - } - } - - private void onBindCommonContact(final ContactHolder holder, final ItemContact contact) { - clearAnimation(contact, holder.progressIndicator); - holder.name.setText(contact.name); - holder.phone.setText(contact.phone); - holder.contactContainer.setVisibility(View.VISIBLE); - holder.undoData.setVisibility(View.GONE); - } - - private void onBindPendingContact(final ContactHolder holder, final ItemContact contact) { - holder.deletedName.setText("You have just deleted " + contact.name); - holder.contactContainer.setVisibility(View.GONE); - holder.undoData.setVisibility(View.VISIBLE); - holder.progressIndicator.setVisibility(View.VISIBLE); - holder.progressIndicator.setX(contact.posX); - - if (pendingRemoveActions.get(contact.name) == null) { - pendingRemoveActions.put(contact.name, new Runnable() { - @Override - public void run() { - removeItem(contact); - } - }); - } - - handler.postDelayed(pendingRemoveActions.get(contact.name), PENDING_DURATION); - final ValueAnimator animator; - if (animatorsMap.get(contact.name) != null) { - animator = animatorsMap.get(contact.name); - ContactAdapterUtils.initAnimator(holder.progressIndicator, contact, animator); - } else { - animator = ContactAdapterUtils.createAnimator(holder.progressIndicator, contact); - animatorsMap.put(contact.name, animator); - } - animator.start(); - } - - private void removeItem(final ItemContact contact) { - final int pos = contacts.indexOf(contact); - handler.removeCallbacks(pendingRemoveActions.remove(contact.name)); - pendingRemoveActions.remove(contact.name); - contacts.remove(contact); - notifyItemRemoved(pos); - ContactAdapterUtils.clearContact(contact); - ContactAdapterUtils.clearAnimator(animatorsMap.remove(contact.name)); - } - - private void clearAnimation(final ItemContact contact, final View progressView) { - ContactAdapterUtils.clearAnimator(animatorsMap.get(contact.name)); - ContactAdapterUtils.clearContact(contact); - ContactAdapterUtils.clearView(progressView); - } -} diff --git a/app/src/main/java/test/alexzander/swipetodelete/ContactAdapterUtils.java b/app/src/main/java/test/alexzander/swipetodelete/ContactAdapterUtils.java deleted file mode 100644 index d9eebb7..0000000 --- a/app/src/main/java/test/alexzander/swipetodelete/ContactAdapterUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -package test.alexzander.swipetodelete; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.view.View; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by AlexZandR on 4/24/17 - */ - -public abstract class ContactAdapterUtils { - - public static final long DELETING_DURATION = 3000; - public static final long PENDING_DURATION = DELETING_DURATION - 150; - - private ContactAdapterUtils() { - } - - public static List prepareContactList(int count) { - List result = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - result.add(new ItemContact("User Name " + String.valueOf(i), "+1234567" + String.valueOf(i))); - } - return result; - } - - public static ValueAnimator createAnimator(final View view, final ItemContact contact) { - return initAnimator(view, contact, null); - } - - public static ValueAnimator initAnimator(final View view, final ItemContact contact, - ValueAnimator animator) { - final float screenWidth = MainActivity.sDeviceScreenWidth; - if (animator == null) { - animator = ValueAnimator.ofFloat(contact.posX, screenWidth * contact.direction); - } else { - animator.removeAllUpdateListeners(); - animator.removeAllListeners(); - animator.setFloatValues(contact.posX, screenWidth * contact.direction); - } - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - final float posX = (Float) animation.getAnimatedValue(); - view.setX(posX); - contact.posX = posX; - } - }); - animator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - contact.isRunningAnimation = true; - view.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Animator animation) { - contact.isRunningAnimation = false; - } - - @Override - public void onAnimationCancel(Animator animation) { - clearContact(contact); - clearView(view); - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - animator.setDuration((long) (DELETING_DURATION * (screenWidth - contact.posX * contact.direction) / screenWidth)); - return animator; - } - - public static void clearAnimator(final ValueAnimator animator) { - if (animator != null) { - animator.cancel(); - animator.removeAllUpdateListeners(); - animator.removeAllListeners(); - } - } - - public static void clearContact(final ItemContact contact) { - if (contact != null) { - contact.isPendingDelete = false; - contact.isRunningAnimation = false; - contact.posX = 0; - } - } - - public static void clearView(final View view) { - if (view != null) { - view.setX(0); - view.setVisibility(View.GONE); - } - } -} diff --git a/app/src/main/java/test/alexzander/swipetodelete/ContactHolder.java b/app/src/main/java/test/alexzander/swipetodelete/ContactHolder.java deleted file mode 100644 index a31e82d..0000000 --- a/app/src/main/java/test/alexzander/swipetodelete/ContactHolder.java +++ /dev/null @@ -1,48 +0,0 @@ -package test.alexzander.swipetodelete; - -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.TextView; - -/** - * Created by AlexZandR on 4/24/17. - */ - -public class ContactHolder extends RecyclerView.ViewHolder { - - public boolean isPendingDelete = false; - public TextView deletedName; - public TextView name; - public TextView phone; - public View progressIndicator; - public View contactContainer; - public View undoContainer; - public View undoData; - - - public ContactHolder(final View itemView, final UndoClickListener listener) { - super(itemView); - deletedName = (TextView) itemView.findViewById(R.id.user_name_deleted); - name = (TextView) itemView.findViewById(R.id.user_name); - phone = (TextView) itemView.findViewById(R.id.user_phone_number); - contactContainer = itemView.findViewById(R.id.contact_container); - undoContainer = itemView.findViewById(R.id.undo_container); - undoData = itemView.findViewById(R.id.undo_data); - itemView.findViewById(R.id.button_undo).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - listener.onUndoClick(getAdapterPosition()); - } - }); - progressIndicator = itemView.findViewById(R.id.progress_indicator); - } - - View getTopContainer() { - if (isPendingDelete) { - return undoContainer; - } else { - return contactContainer; - } - } -} diff --git a/app/src/main/java/test/alexzander/swipetodelete/ContactItemTouchCallback.java b/app/src/main/java/test/alexzander/swipetodelete/ContactItemTouchCallback.java deleted file mode 100644 index 654eb5e..0000000 --- a/app/src/main/java/test/alexzander/swipetodelete/ContactItemTouchCallback.java +++ /dev/null @@ -1,65 +0,0 @@ -package test.alexzander.swipetodelete; - -import android.graphics.Canvas; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; - -/** - * Created by AlexZandR on 4/24/17 - */ - -public class ContactItemTouchCallback extends ItemTouchHelper.Callback { - - private int swipeDirs = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.END | - ItemTouchHelper.START; - private ItemSwipeListener listener; - - public ContactItemTouchCallback(ItemSwipeListener listener) { - this.listener = listener; - } - - @Override - public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - return makeMovementFlags(0, swipeDirs); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { - if (listener != null) { - listener.onItemSwiped((ContactHolder) viewHolder, swipeDir); - } - } - - @Override - public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - getDefaultUIUtil().clearView(((ContactHolder) viewHolder).getTopContainer()); - } - - @Override - public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { - if (viewHolder != null) { - getDefaultUIUtil().onSelected(((ContactHolder) viewHolder).getTopContainer()); - } - } - - @Override - public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder - , float dX, float dY, int actionState, boolean isCurrentlyActive) { - getDefaultUIUtil().onDraw(c, recyclerView, - ((ContactHolder) viewHolder).getTopContainer(), dX, dY, - actionState, isCurrentlyActive); - } - - @Override - public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder - , float dX, float dY, int actionState, boolean isCurrentlyActive) { - getDefaultUIUtil().onDrawOver(c, recyclerView, - ((ContactHolder) viewHolder).getTopContainer(), dX, dY, - actionState, isCurrentlyActive); - } -} diff --git a/app/src/main/java/test/alexzander/swipetodelete/ItemContact.java b/app/src/main/java/test/alexzander/swipetodelete/ItemContact.java deleted file mode 100644 index 35011cf..0000000 --- a/app/src/main/java/test/alexzander/swipetodelete/ItemContact.java +++ /dev/null @@ -1,33 +0,0 @@ -package test.alexzander.swipetodelete; - -import android.support.v7.widget.helper.ItemTouchHelper; - -/** - * Created by AlexZandR on 4/24/17 - */ - -public class ItemContact { - - public static final int LEFT = -1; - public static final int RIGHT = 1; - - boolean isPendingDelete = false; - boolean isRunningAnimation = false; - int direction; - float posX = 0; - String name; - String phone; - - public ItemContact(String name, String phone) { - this.name = name; - this.phone = phone; - } - - void setDirection(int swipeDir) { - if (ItemTouchHelper.LEFT == swipeDir || ItemTouchHelper.START == swipeDir) { - direction = LEFT; - } else { - direction = RIGHT; - } - } -} diff --git a/app/src/main/java/test/alexzander/swipetodelete/ItemSwipeListener.java b/app/src/main/java/test/alexzander/swipetodelete/ItemSwipeListener.java deleted file mode 100644 index 3f1a6b7..0000000 --- a/app/src/main/java/test/alexzander/swipetodelete/ItemSwipeListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package test.alexzander.swipetodelete; - -/** - * Created by AlexZandR on 4/24/17 - */ - -public interface ItemSwipeListener { - void onItemSwiped(ContactHolder holder, int swipeDir); -} diff --git a/app/src/main/java/test/alexzander/swipetodelete/MainActivity.java b/app/src/main/java/test/alexzander/swipetodelete/MainActivity.java deleted file mode 100644 index 5d3afcc..0000000 --- a/app/src/main/java/test/alexzander/swipetodelete/MainActivity.java +++ /dev/null @@ -1,77 +0,0 @@ -package test.alexzander.swipetodelete; - -import android.content.Context; -import android.graphics.Point; -import android.os.Bundle; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.view.Display; -import android.view.View; -import android.view.WindowManager; - -import static android.widget.LinearLayout.VERTICAL; - -public class MainActivity extends AppCompatActivity { - - public static int sDeviceScreenWidth; - private ContactAdapter adapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - deviceWidth(); - setContentView(R.layout.activity_main); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); - fab.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Snackbar.make(view, "Navigate to the Create Contact screen ", Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - } - }); - - initRecyclerView(); - } - - @Override - protected void onResume() { - super.onResume(); - if (adapter != null) { - adapter.notifyDataSetChanged(); - } - } - - private void initRecyclerView() { - - RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); - recyclerView.setLayoutManager(new LinearLayoutManager(this, VERTICAL, false)); - - adapter = new ContactAdapter(ContactAdapterUtils.prepareContactList(50)); - recyclerView.setAdapter(adapter); - - DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, VERTICAL); - recyclerView.addItemDecoration(dividerItemDecoration); - - ItemTouchHelper.Callback touchCallback = new ContactItemTouchCallback(adapter); - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchCallback); - itemTouchHelper.attachToRecyclerView(recyclerView); - } - - private void deviceWidth() { - WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - - Point size = new Point(); - display.getSize(size); - sDeviceScreenWidth = size.x; - } -} diff --git a/app/src/main/java/test/alexzander/swipetodelete/UndoClickListener.java b/app/src/main/java/test/alexzander/swipetodelete/UndoClickListener.java deleted file mode 100644 index 56e99b6..0000000 --- a/app/src/main/java/test/alexzander/swipetodelete/UndoClickListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package test.alexzander.swipetodelete; - -/** - * Created by AlexZandR on 4/24/17 - */ - -public interface UndoClickListener { - void onUndoClick(int position); -} diff --git a/app/src/main/res/layout/activity_java.xml b/app/src/main/res/layout/activity_java.xml new file mode 100644 index 0000000..c1cf97e --- /dev/null +++ b/app/src/main/res/layout/activity_java.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + \ 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 d399bb2..718dab9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,10 +1,11 @@ - + tools:context="agilie.example.swipe2delete.kotlin.UsersActivity"> + app:popupTheme="@style/AppTheme.PopupOverlay"/> - - - + diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/activity_main_item.xml similarity index 85% rename from app/src/main/res/layout/list_item.xml rename to app/src/main/res/layout/activity_main_item.xml index 4752f7c..2a033c0 100644 --- a/app/src/main/res/layout/list_item.xml +++ b/app/src/main/res/layout/activity_main_item.xml @@ -1,9 +1,9 @@ + android:layout_width="match_parent" + android:layout_height="72dp" + android:animateLayoutChanges="true" + android:gravity="center_vertical|start"> + android:visibility="gone"/> + android:paddingStart="16dp"> + android:textColor="@android:color/white"/>