├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── drawable-hdpi │ │ │ ├── img.png │ │ │ ├── ic_back.png │ │ │ ├── img_bad.png │ │ │ ├── img_born.png │ │ │ ├── img_dead.png │ │ │ ├── img_hard.png │ │ │ ├── img_hater.png │ │ │ ├── img_hells.png │ │ │ ├── img_iron.png │ │ │ ├── img_jesus.png │ │ │ ├── img_pain.png │ │ │ ├── img_shook.png │ │ │ ├── img_stay.png │ │ │ ├── img_freedom.png │ │ │ ├── img_guilty.png │ │ │ ├── img_psycho.png │ │ │ ├── img_rolling.png │ │ │ ├── img_sandman.png │ │ │ ├── img_someone.png │ │ │ ├── img_thefire.png │ │ │ ├── img_are_you_.png │ │ │ ├── img_badblood.png │ │ │ ├── img_doing_it.png │ │ │ ├── img_light_up.png │ │ │ ├── img_obstacles.png │ │ │ ├── img_siberian.png │ │ │ ├── img_ten_tonne.png │ │ │ ├── img_way_down.png │ │ │ ├── ic_done_white_24dp.png │ │ │ └── img_blackskinhead.png │ │ ├── drawable-mdpi │ │ │ ├── img.png │ │ │ ├── ic_back.png │ │ │ ├── img_bad.png │ │ │ ├── img_born.png │ │ │ ├── img_dead.png │ │ │ ├── img_hard.png │ │ │ ├── img_hater.png │ │ │ ├── img_hells.png │ │ │ ├── img_iron.png │ │ │ ├── img_jesus.png │ │ │ ├── img_pain.png │ │ │ ├── img_shook.png │ │ │ ├── img_stay.png │ │ │ ├── img_freedom.png │ │ │ ├── img_guilty.png │ │ │ ├── img_psycho.png │ │ │ ├── img_rolling.png │ │ │ ├── img_sandman.png │ │ │ ├── img_someone.png │ │ │ ├── img_thefire.png │ │ │ ├── img_are_you_.png │ │ │ ├── img_badblood.png │ │ │ ├── img_doing_it.png │ │ │ ├── img_obstacles.png │ │ │ ├── img_siberian.png │ │ │ ├── img_ten_tonne.png │ │ │ ├── img_way_down.png │ │ │ ├── ic_done_white_24dp.png │ │ │ └── img_blackskinhead.png │ │ ├── drawable-xhdpi │ │ │ ├── img.png │ │ │ ├── ic_back.png │ │ │ ├── img_bad.png │ │ │ ├── img_born.png │ │ │ ├── img_dead.png │ │ │ ├── img_hard.png │ │ │ ├── img_iron.png │ │ │ ├── img_pain.png │ │ │ ├── img_stay.png │ │ │ ├── img_guilty.png │ │ │ ├── img_hater.png │ │ │ ├── img_hells.png │ │ │ ├── img_jesus.png │ │ │ ├── img_psycho.png │ │ │ ├── img_shook.png │ │ │ ├── img_are_you_.png │ │ │ ├── img_badblood.png │ │ │ ├── img_doing_it.png │ │ │ ├── img_freedom.png │ │ │ ├── img_obstacles.png │ │ │ ├── img_rolling.png │ │ │ ├── img_sandman.png │ │ │ ├── img_siberian.png │ │ │ ├── img_someone.png │ │ │ ├── img_ten_tonne.png │ │ │ ├── img_thefire.png │ │ │ ├── img_way_down.png │ │ │ ├── ic_done_white_24dp.png │ │ │ └── img_blackskinhead.png │ │ ├── drawable-xxhdpi │ │ │ ├── img.png │ │ │ ├── ic_back.png │ │ │ ├── img_bad.png │ │ │ ├── img_born.png │ │ │ ├── img_dead.png │ │ │ ├── img_hard.png │ │ │ ├── img_hater.png │ │ │ ├── img_hells.png │ │ │ ├── img_iron.png │ │ │ ├── img_jesus.png │ │ │ ├── img_pain.png │ │ │ ├── img_shook.png │ │ │ ├── img_stay.png │ │ │ ├── img_are_you_.png │ │ │ ├── img_badblood.png │ │ │ ├── img_doing_it.png │ │ │ ├── img_freedom.png │ │ │ ├── img_guilty.png │ │ │ ├── img_psycho.png │ │ │ ├── img_rolling.png │ │ │ ├── img_sandman.png │ │ │ ├── img_siberian.png │ │ │ ├── img_someone.png │ │ │ ├── img_thefire.png │ │ │ ├── img_way_down.png │ │ │ ├── img_obstacles.png │ │ │ ├── img_ten_tonne.png │ │ │ ├── img_blackskinhead.png │ │ │ └── ic_done_white_24dp.png │ │ ├── drawable-xxxhdpi │ │ │ ├── img.png │ │ │ ├── ic_back.png │ │ │ ├── img_bad.png │ │ │ ├── img_born.png │ │ │ ├── img_dead.png │ │ │ ├── img_hard.png │ │ │ ├── img_iron.png │ │ │ ├── img_pain.png │ │ │ ├── img_stay.png │ │ │ ├── img_freedom.png │ │ │ ├── img_guilty.png │ │ │ ├── img_hater.png │ │ │ ├── img_hells.png │ │ │ ├── img_jesus.png │ │ │ ├── img_psycho.png │ │ │ ├── img_rolling.png │ │ │ ├── img_sandman.png │ │ │ ├── img_shook.png │ │ │ ├── img_someone.png │ │ │ ├── img_thefire.png │ │ │ ├── img_are_you_.png │ │ │ ├── img_badblood.png │ │ │ ├── img_doing_it.png │ │ │ ├── img_obstacles.png │ │ │ ├── img_siberian.png │ │ │ ├── img_ten_tonne.png │ │ │ ├── img_way_down.png │ │ │ ├── ic_done_white_24dp.png │ │ │ └── img_blackskinhead.png │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher_app.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher_app.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher_app.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher_app.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher_app.png │ │ ├── values │ │ │ ├── ids.xml │ │ │ ├── dimens.xml │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── drawable-v21 │ │ │ └── item_background.xml │ │ ├── drawable │ │ │ ├── toolbar_bg.xml │ │ │ ├── item_background.xml │ │ │ └── ripple.xml │ │ ├── menu │ │ │ └── menu.xml │ │ ├── values-v21 │ │ │ └── styles.xml │ │ └── layout │ │ │ ├── activity_main.xml │ │ │ └── item_view.xml │ │ ├── java │ │ └── com │ │ │ └── yalantis │ │ │ └── multiselectdemo │ │ │ └── demo │ │ │ ├── Callback.java │ │ │ ├── TracksItemDecorator.java │ │ │ ├── ViewHolder.java │ │ │ ├── model │ │ │ ├── Track.java │ │ │ └── TrackList.java │ │ │ ├── RightAdapter.java │ │ │ ├── LeftAdapter.java │ │ │ └── DemoActivity.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── multiselection ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── ids.xml │ │ │ │ ├── colors.xml │ │ │ │ └── dimens.xml │ │ │ ├── drawable │ │ │ │ ├── yal_ms_round_left.xml │ │ │ │ ├── yal_ms_round_right.xml │ │ │ │ ├── yal_ms_left_side.xml │ │ │ │ └── yal_ms_right_side.xml │ │ │ └── layout │ │ │ │ ├── yal_ms_multiselect.xml │ │ │ │ ├── yal_ms_page_left.xml │ │ │ │ └── yal_ms_page_right.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── yalantis │ │ │ │ └── multiselection │ │ │ │ └── lib │ │ │ │ ├── callbacks │ │ │ │ ├── ListInterface.kt │ │ │ │ └── SortedListCallback.kt │ │ │ │ ├── MultiSelect.kt │ │ │ │ ├── ZoomPageTransformer.kt │ │ │ │ ├── adapter │ │ │ │ ├── BaseRightAdapter.kt │ │ │ │ ├── BaseAdapter.kt │ │ │ │ ├── BaseLeftAdapter.kt │ │ │ │ └── ViewPagerAdapter.kt │ │ │ │ ├── MultiSelectViewPager.kt │ │ │ │ ├── util │ │ │ │ └── Extesions.kt │ │ │ │ ├── MultiSelectBuilder.kt │ │ │ │ ├── MultiSelectImpl.kt │ │ │ │ └── MultiSelectItemAnimator.kt │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── com │ │ └── yalantis │ │ └── multiselection │ │ └── ExampleUnitTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── imgs └── dribble.gif ├── keys └── keystore.jks ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .gitlab-ci.yml ├── LICENSE ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /multiselection/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':multiselection' 2 | -------------------------------------------------------------------------------- /imgs/dribble.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/imgs/dribble.gif -------------------------------------------------------------------------------- /keys/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/keys/keystore.jks -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img.png -------------------------------------------------------------------------------- /multiselection/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Multi Selection 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_bad.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_born.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_born.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_dead.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_hard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_hard.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_hater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_hater.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_hells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_hells.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_iron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_iron.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_jesus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_jesus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_pain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_pain.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_shook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_shook.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_stay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_stay.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_bad.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_born.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_born.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_dead.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_hard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_hard.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_hater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_hater.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_hells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_hells.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_iron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_iron.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_jesus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_jesus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_pain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_pain.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_shook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_shook.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_stay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_stay.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_bad.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_born.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_born.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_dead.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_hard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_hard.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_iron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_iron.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_pain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_pain.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_stay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_stay.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_bad.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_freedom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_freedom.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_guilty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_guilty.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_psycho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_psycho.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_rolling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_rolling.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_sandman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_sandman.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_someone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_someone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_thefire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_thefire.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_freedom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_freedom.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_guilty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_guilty.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_psycho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_psycho.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_rolling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_rolling.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_sandman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_sandman.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_someone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_someone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_thefire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_thefire.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_guilty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_guilty.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_hater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_hater.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_hells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_hells.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_jesus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_jesus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_psycho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_psycho.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_shook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_shook.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_born.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_born.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_dead.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_hard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_hard.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_hater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_hater.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_hells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_hells.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_iron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_iron.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_jesus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_jesus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_pain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_pain.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_shook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_shook.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_stay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_stay.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_bad.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_born.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_born.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_dead.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_hard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_hard.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_iron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_iron.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_pain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_pain.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_stay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_stay.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_are_you_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_are_you_.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_badblood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_badblood.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_doing_it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_doing_it.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_light_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_light_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_obstacles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_obstacles.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_siberian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_siberian.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_ten_tonne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_ten_tonne.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_way_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_way_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_are_you_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_are_you_.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_badblood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_badblood.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_doing_it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_doing_it.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_obstacles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_obstacles.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_siberian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_siberian.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_ten_tonne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_ten_tonne.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_way_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_way_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_are_you_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_are_you_.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_badblood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_badblood.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_doing_it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_doing_it.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_freedom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_freedom.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_obstacles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_obstacles.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_rolling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_rolling.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_sandman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_sandman.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_siberian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_siberian.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_someone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_someone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_ten_tonne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_ten_tonne.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_thefire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_thefire.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_way_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_way_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_are_you_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_are_you_.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_badblood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_badblood.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_doing_it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_doing_it.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_freedom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_freedom.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_guilty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_guilty.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_psycho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_psycho.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_rolling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_rolling.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_sandman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_sandman.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_siberian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_siberian.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_someone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_someone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_thefire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_thefire.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_way_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_way_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_freedom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_freedom.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_guilty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_guilty.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_hater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_hater.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_hells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_hells.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_jesus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_jesus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_psycho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_psycho.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_rolling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_rolling.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_sandman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_sandman.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_shook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_shook.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_someone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_someone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_thefire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_thefire.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_obstacles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_obstacles.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_ten_tonne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_ten_tonne.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_are_you_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_are_you_.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_badblood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_badblood.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_doing_it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_doing_it.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_obstacles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_obstacles.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_siberian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_siberian.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_ten_tonne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_ten_tonne.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_way_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_way_down.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_done_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/ic_done_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_blackskinhead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-hdpi/img_blackskinhead.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_done_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/ic_done_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_blackskinhead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-mdpi/img_blackskinhead.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_done_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/ic_done_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_blackskinhead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xhdpi/img_blackskinhead.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_blackskinhead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/img_blackskinhead.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_done_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/ic_done_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_blackskinhead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Multi-Selection/HEAD/app/src/main/res/drawable-xxxhdpi/img_blackskinhead.png -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /multiselection/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | 8dp 5 | 1.5dp 6 | -------------------------------------------------------------------------------- /multiselection/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #101010 4 | #c0c0c0 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/yalantis/multiselectdemo/demo/Callback.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselectdemo.demo; 2 | 3 | /** 4 | * Created by Artem Kholodnyi on 9/6/16. 5 | */ 6 | interface Callback { 7 | void onClick(int position); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/toolbar_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 17 19:15:26 EEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #161618 7 | 8 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - export ANDROID_HOME=/opt/android 3 | - uname -a 4 | 5 | build: 6 | stage: build 7 | script: 8 | - ./gradlew clean assembleRelease 9 | artifacts: 10 | name: "release-${CI_BUILD_REF_NAME}" 11 | paths: 12 | - ./app/build/outputs/apk/app-release.apk 13 | 14 | -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/callbacks/ListInterface.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib.callbacks 2 | 3 | /** 4 | * Created by Artem Kholodnyi on 9/6/16. 5 | */ 6 | interface ListInterface { 7 | fun removeItemAt(position: Int): I 8 | fun indexOf(item: I): Int 9 | fun add(item: I): Int 10 | } -------------------------------------------------------------------------------- /multiselection/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25dp 4 | 1.5dp 5 | 6 | 3dp 7 | -3dp 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /multiselection/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /multiselection/src/main/res/drawable/yal_ms_round_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /multiselection/src/main/res/drawable/yal_ms_round_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /multiselection/src/test/java/com/yalantis/multiselection/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Multi Select 3 | Create Playlist 4 | Don\'t you like anything? :\'( 5 | 6 | 7 | You selected %d song. Good taste! 8 | You selected %d songs. Good taste! 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /multiselection/src/main/res/layout/yal_ms_multiselect.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /multiselection/src/main/res/drawable/yal_ms_left_side.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /multiselection/src/main/res/drawable/yal_ms_right_side.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/ice/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interfaces 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interfaces.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /multiselection/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/ice/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interfaces 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interfaces.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/yalantis/multiselectdemo/demo/TracksItemDecorator.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselectdemo.demo; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | 7 | /** 8 | * Created by Artem Kholodnyi on 9/6/16. 9 | */ 10 | public class TracksItemDecorator extends RecyclerView.ItemDecoration { 11 | 12 | private int size; 13 | 14 | public TracksItemDecorator(int size) { 15 | this.size = size; 16 | } 17 | 18 | @Override 19 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 20 | outRect.bottom = size; 21 | outRect.top = 0; 22 | outRect.left = 0; 23 | outRect.right = 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/MultiSelect.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import com.yalantis.multiselection.lib.adapter.BaseLeftAdapter 5 | import com.yalantis.multiselection.lib.adapter.BaseRightAdapter 6 | 7 | interface MultiSelect> { 8 | 9 | val recyclerLeft: RecyclerView 10 | val recyclerRight: RecyclerView 11 | 12 | val selectedItems: List? 13 | 14 | var leftAdapter: BaseLeftAdapter? 15 | var rightAdapter: BaseRightAdapter? 16 | 17 | fun select(position: Int) 18 | fun deselect(position: Int) 19 | 20 | fun showSelectedPage() 21 | fun showNotSelectedPage() 22 | 23 | fun setSidebarWidthDp(iconWidthDp: Float) 24 | 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /multiselection/src/main/res/layout/yal_ms_page_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /multiselection/src/main/res/layout/yal_ms_page_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/ZoomPageTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib 2 | 3 | import android.support.v4.view.ViewPager 4 | import android.view.View 5 | import com.yalantis.multiselection.lib.util.mix 6 | import com.yalantis.multiselection.lib.util.setScaleXY 7 | import com.yalantis.multiselection.lib.util.smoothstep 8 | 9 | class ZoomPageTransformer(val pageWidth: Float) : ViewPager.PageTransformer { 10 | 11 | companion object { 12 | const val MIN_ZOOM = 0.8f 13 | } 14 | 15 | private val sidebarWidth: Float 16 | 17 | init { 18 | sidebarWidth = 1f - pageWidth 19 | } 20 | 21 | override fun transformPage(page: View, position: Float) { 22 | val scale = when { 23 | // left page is never scaled 24 | position <= 0 -> 1f 25 | else -> position.smoothstep(pageWidth, sidebarWidth).mix(1f, MIN_ZOOM) 26 | } 27 | 28 | page.pivotX = 0f 29 | page.setScaleXY(scale) 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/callbacks/SortedListCallback.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib.callbacks 2 | 3 | import android.support.v7.util.SortedList.Callback 4 | 5 | /** 6 | * Created by Artem Kholodnyi on 9/4/16. 7 | */ 8 | open class SortedListCallback> : Callback() { 9 | 10 | override fun areItemsTheSame(item1: T, item2: T): Boolean { 11 | return item1 == item2 12 | } 13 | 14 | override fun compare(o1: T, o2: T): Int { 15 | return o1.compareTo(o2) 16 | } 17 | 18 | override fun areContentsTheSame(oldItem: T, newItem: T): Boolean { 19 | return oldItem.equals(newItem) 20 | } 21 | 22 | override fun onChanged(position: Int, count: Int) { 23 | 24 | } 25 | 26 | override fun onRemoved(position: Int, count: Int) { 27 | 28 | } 29 | 30 | override fun onInserted(position: Int, count: Int) { 31 | 32 | } 33 | 34 | override fun onMoved(fromPosition: Int, toPosition: Int) { 35 | 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /multiselection/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 24 6 | buildToolsVersion "24.0.2" 7 | 8 | defaultConfig { 9 | minSdkVersion 17 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | sourceSets { 21 | main.java.srcDirs += 'src/main/kotlin' 22 | } 23 | resourcePrefix "yal_ms_" 24 | 25 | } 26 | 27 | dependencies { 28 | compile fileTree(dir: 'libs', include: ['*.jar']) 29 | testCompile 'junit:junit:4.12' 30 | compile "com.android.support:appcompat-v7:$support_version" 31 | compile "com.android.support:recyclerview-v7:$support_version" 32 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 33 | } 34 | repositories { 35 | mavenCentral() 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/yalantis/multiselectdemo/demo/ViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselectdemo.demo; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.ImageView; 6 | import android.widget.TextView; 7 | 8 | import com.yalantis.multiselectdemo.R; 9 | import com.yalantis.multiselectdemo.demo.model.Track; 10 | 11 | /** 12 | * Created by Artem Kholodnyi on 9/6/16. 13 | */ 14 | class ViewHolder extends RecyclerView.ViewHolder { 15 | TextView track; 16 | TextView artist; 17 | ImageView avatar; 18 | 19 | public ViewHolder(View view) { 20 | super(view); 21 | track = (TextView) view.findViewById(R.id.track); 22 | artist = (TextView) view.findViewById(R.id.artist); 23 | avatar = (ImageView) view.findViewById(R.id.yal_ms_avatar); 24 | } 25 | 26 | public static void bind(ViewHolder viewHolder, Track track) { 27 | viewHolder.track.setText(track.getTrackName()); 28 | viewHolder.artist.setText(track.getArtist()); 29 | viewHolder.avatar.setImageResource(track.getAlbum()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yalantis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 23 | 24 | 25 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/adapter/BaseRightAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib.adapter 2 | 3 | import android.support.v7.widget.RecyclerView 4 | 5 | /** 6 | * Created by Artem Kholodnyi on 9/6/16. 7 | */ 8 | 9 | abstract class BaseRightAdapter : BaseAdapter() { 10 | 11 | var items: MutableList = mutableListOf() 12 | 13 | override fun getItemCount(): Int = items.count() 14 | 15 | override fun getItemAt(index: Int): I = items[index] 16 | 17 | override fun indexOf(item: I): Int = items.indexOf(item) 18 | 19 | override fun removeItemAt(position: Int): I { 20 | return items.removeAt(position).apply { 21 | notifyItemRemoved(position) 22 | } 23 | } 24 | 25 | override fun add(item: I, hide: Boolean): Int { 26 | if (hide) { 27 | hiddenItems += item 28 | } 29 | items.add(item) 30 | val index = items.count() - 1 31 | notifyItemInserted(index) 32 | return index 33 | } 34 | 35 | 36 | override fun addAll(items: List) { 37 | this.items.addAll(items) 38 | } 39 | } -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/adapter/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib.adapter 2 | 3 | import android.support.annotation.CallSuper 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.View 6 | 7 | /** 8 | * Created by Artem Kholodnyi on 9/6/16. 9 | */ 10 | abstract class BaseAdapter : RecyclerView.Adapter() { 11 | 12 | val hiddenItems = mutableSetOf() 13 | 14 | abstract fun add(item: I, hide: Boolean = false): Int 15 | 16 | abstract fun removeItemAt(position: Int): I 17 | 18 | abstract fun indexOf(item: I): Int 19 | 20 | abstract fun getItemAt(index: Int): I 21 | 22 | abstract fun addAll(items: List) 23 | 24 | fun showItem(item: I) { 25 | if (hiddenItems.remove(item)) { 26 | notifyItemChanged(indexOf(item)) 27 | } 28 | notifyDataSetChanged() 29 | } 30 | 31 | @CallSuper 32 | override fun onBindViewHolder(holder: VH, position: Int) { 33 | holder.itemView.visibility = when { 34 | hiddenItems.contains(getItemAt(position)) -> View.INVISIBLE 35 | else -> View.VISIBLE 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yalantis/multiselectdemo/demo/model/Track.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselectdemo.demo.model; 2 | 3 | import android.support.annotation.DrawableRes; 4 | import android.support.v4.app.Fragment; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * Created by Artem Kholodnyi on 9/6/16. 10 | */ 11 | public class Track implements Comparable, Serializable { 12 | private String trackName; 13 | private @DrawableRes int album; 14 | private String artist; 15 | 16 | public Track(String trackName, String artist, @DrawableRes int album) { 17 | this.trackName = trackName; 18 | this.artist = artist; 19 | this.album = album; 20 | } 21 | 22 | public String getTrackName() { 23 | return trackName; 24 | } 25 | 26 | public void setTrackName(String trackName) { 27 | this.trackName = trackName; 28 | } 29 | 30 | public String getArtist() { 31 | return artist; 32 | } 33 | 34 | public void setArtist(String artist) { 35 | this.artist = artist; 36 | } 37 | 38 | public int getAlbum() { 39 | return album; 40 | } 41 | 42 | public void setAlbum(int album) { 43 | this.album = album; 44 | } 45 | 46 | @Override 47 | public int compareTo(Track track) { 48 | return trackName.compareTo(track.trackName); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/MultiSelectViewPager.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib 2 | 3 | import android.content.Context 4 | import android.support.v4.view.ViewPager 5 | import android.util.AttributeSet 6 | import android.util.Log 7 | import android.view.MotionEvent 8 | 9 | /** 10 | * Created by Artem Kholodnyi on 9/2/16. 11 | */ 12 | 13 | class MultiSelectViewPager : ViewPager { 14 | 15 | /** 16 | * @return true if should intercept this click 17 | */ 18 | var onClickCallback: (Float, Float) -> Boolean = { x, y -> false } 19 | 20 | constructor(context: Context?) : super(context) 21 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 22 | 23 | private var lastDownX: Float = -1f 24 | private var lastDownY: Float = -1f 25 | 26 | override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { 27 | if (ev != null) { 28 | when (ev.action) { 29 | MotionEvent.ACTION_DOWN -> { 30 | lastDownX = ev.x 31 | lastDownY = ev.y 32 | } 33 | MotionEvent.ACTION_UP -> { 34 | Log.d("cmp", "$lastDownX") 35 | return onClickCallback(lastDownX, lastDownY) 36 | } 37 | } 38 | } 39 | 40 | return super.onInterceptTouchEvent(ev) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/adapter/BaseLeftAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib.adapter 2 | 3 | import android.support.v7.util.SortedList 4 | import android.support.v7.widget.RecyclerView 5 | import com.yalantis.multiselection.lib.callbacks.SortedListCallback 6 | 7 | /** 8 | * Created by Artem Kholodnyi on 9/3/16. 9 | */ 10 | abstract class BaseLeftAdapter, VH : RecyclerView.ViewHolder> 11 | : BaseAdapter { 12 | 13 | lateinit var items: SortedList 14 | 15 | private constructor() : super() 16 | 17 | constructor(klass: Class) : super() { 18 | items = SortedList(klass, object: SortedListCallback() {}) 19 | } 20 | 21 | override fun getItemCount(): Int = items.size() 22 | 23 | override fun indexOf(item: I): Int = items.indexOf(item) 24 | 25 | override fun removeItemAt(position: Int): I = items.removeItemAt(position).apply { 26 | notifyItemRemoved(position) 27 | } 28 | 29 | override fun add(item: I, hide: Boolean): Int { 30 | return items.add(item).apply { 31 | notifyItemInserted(this) 32 | if (hide) { 33 | hiddenItems += item 34 | } 35 | } 36 | } 37 | 38 | override fun addAll(list: List) { 39 | items.addAll(list) 40 | } 41 | 42 | override fun getItemAt(index: Int): I = items.get(index) 43 | } -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/adapter/ViewPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib.adapter 2 | 3 | import android.support.v4.view.PagerAdapter 4 | import android.support.v4.view.ViewPager 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import java.io.Serializable 9 | 10 | /** 11 | * Created by Artem Kholodnyi on 8/17/16. 12 | */ 13 | class ViewPagerAdapter(val pageWidth: Float) : PagerAdapter() { 14 | 15 | lateinit var pageLeft: View 16 | lateinit var pageRight: View 17 | 18 | override fun getPageWidth(position: Int): Float { 19 | return pageWidth // this should be configurable (dp) 20 | } 21 | 22 | override fun getCount(): Int = 2 23 | 24 | override fun isViewFromObject(view: View, `object`: Any): Boolean { 25 | return view == `object` 26 | } 27 | 28 | override fun instantiateItem(container: ViewGroup?, position: Int): Any { 29 | val pager = container as ViewPager 30 | val view = getView(position, pager) 31 | pager.addView(view) 32 | view.tag = position 33 | return view 34 | } 35 | 36 | private fun getView(position: Int, pager: ViewPager): View = when (position) { 37 | 0 -> pageLeft 38 | 1 -> pageRight 39 | else -> throw IllegalStateException() 40 | } 41 | 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/yalantis/multiselectdemo/demo/RightAdapter.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselectdemo.demo; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | import com.yalantis.multiselectdemo.R; 8 | import com.yalantis.multiselectdemo.demo.model.Track; 9 | import com.yalantis.multiselection.lib.adapter.BaseRightAdapter; 10 | 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | /** 14 | * Created by Artem Kholodnyi on 9/6/16. 15 | */ 16 | public class RightAdapter extends BaseRightAdapter { 17 | 18 | private final Callback callback; 19 | 20 | public RightAdapter(Callback callback) { 21 | this.callback = callback; 22 | } 23 | 24 | @Override 25 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 26 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false); 27 | return new ViewHolder(view); 28 | } 29 | 30 | @Override 31 | public void onBindViewHolder(@NotNull final ViewHolder holder, int position) { 32 | super.onBindViewHolder(holder, position); 33 | 34 | ViewHolder.bind(holder, getItemAt(position)); 35 | 36 | holder.itemView.setOnClickListener(view -> { 37 | view.setPressed(true); 38 | view.postDelayed(() -> { 39 | view.setPressed(false); 40 | callback.onClick(holder.getAdapterPosition()); 41 | }, 200); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/yalantis/multiselectdemo/demo/LeftAdapter.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselectdemo.demo; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.yalantis.multiselectdemo.R; 9 | import com.yalantis.multiselectdemo.demo.model.Track; 10 | import com.yalantis.multiselection.lib.adapter.BaseLeftAdapter; 11 | 12 | /** 13 | * Created by Artem Kholodnyi on 9/3/16. 14 | */ 15 | public class LeftAdapter extends BaseLeftAdapter{ 16 | 17 | private final Callback callback; 18 | 19 | public LeftAdapter(Callback callback) { 20 | super(Track.class); 21 | this.callback = callback; 22 | } 23 | 24 | @Override 25 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 26 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false); 27 | return new ViewHolder(view); 28 | } 29 | 30 | @Override 31 | public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { 32 | super.onBindViewHolder(holder, position); 33 | 34 | ViewHolder.bind(holder, getItemAt(position)); 35 | 36 | holder.itemView.setOnClickListener(view -> { 37 | view.setPressed(true); 38 | view.postDelayed(() -> { 39 | view.setPressed(false); 40 | callback.onClick(holder.getAdapterPosition()); 41 | }, 200); 42 | }); 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/util/Extesions.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib.util 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.view.ViewTreeObserver 6 | import kotlin.jvm.internal.Lambda 7 | 8 | /** 9 | * Created by Artem Kholodnyi on 8/23/16. 10 | */ 11 | fun View.removeFromParent() { 12 | val parent = this.parent 13 | if (parent is ViewGroup) { 14 | parent.removeView(this) 15 | } 16 | } 17 | 18 | inline fun T.afterMeasured(crossinline f: T.() -> Unit) { 19 | viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener { 20 | override fun onGlobalLayout() { 21 | if (measuredHeight > 0 && measuredWidth > 0) { 22 | viewTreeObserver.removeOnGlobalLayoutListener(this) 23 | f() 24 | } 25 | } 26 | }) 27 | } 28 | 29 | fun View.setScaleXY(scale: Float) { 30 | scaleX = scale 31 | scaleY = scale 32 | } 33 | 34 | fun View.getLocationOnScreen(): IntArray { 35 | val loc = intArrayOf(0, 0) 36 | this.getLocationOnScreen(loc) 37 | return loc 38 | } 39 | 40 | // Please refer to GLSL docs to understand these functions [[ 41 | 42 | fun Float.clamp(floor: Float, ceil: Float): Float = Math.min(ceil, Math.max(floor, this)) 43 | 44 | fun Float.mix(x: Float, y: Float): Float = x * this + y * (1f - this) 45 | 46 | fun Float.smoothstep(edge0: Float, edge1: Float): Float { 47 | val t = ((this - edge0) / (edge1 - edge0)).clamp(0f, 1f) 48 | return t * t * (3f - 2f * t) 49 | } 50 | 51 | // ]] 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 26 | 27 | 36 | 37 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.yalantis.multiselect.demo" 9 | targetSdkVersion 24 10 | minSdkVersion 17 11 | versionCode 1 12 | versionName "1.0" 13 | jackOptions { 14 | enabled true 15 | additionalParameters('jack.incremental': 'true') 16 | } 17 | } 18 | 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_1_8 21 | targetCompatibility JavaVersion.VERSION_1_8 22 | } 23 | 24 | dexOptions { 25 | javaMaxHeapSize '4096m' 26 | } 27 | 28 | signingConfigs { 29 | release { 30 | storeFile file(RELEASE_STORE_FILE) 31 | storePassword RELEASE_KEYSTORE_PASSWORD 32 | keyAlias RELEASE_KEY_ALIAS_NAME 33 | keyPassword RELEASE_KEY_ALIAS_PASSWORD 34 | } 35 | } 36 | 37 | buildTypes { 38 | release { 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 41 | signingConfig signingConfigs.release 42 | } 43 | } 44 | 45 | } 46 | 47 | dependencies { 48 | compile fileTree(include: ['*.jar'], dir: 'libs') 49 | 50 | compile project(path: ':multiselection') 51 | // in real project use this: 52 | //compile 'com.github.yalantis:multi-selection:v0.1' 53 | 54 | compile "com.android.support:support-v4:$support_version" 55 | compile "com.android.support:appcompat-v7:$support_version" 56 | compile "com.android.support:recyclerview-v7:$support_version" 57 | compile 'com.android.support:design:24.2.0' 58 | 59 | } 60 | repositories { 61 | mavenCentral() 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi-Selection 2 | 3 | [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)]() 4 | [![](https://jitpack.io/v/yalantis/multi-selection.svg)](https://jitpack.io/#yalantis/multi-selection) 5 | [![Yalantis](https://raw.githubusercontent.com/Yalantis/PullToRefresh/develop/PullToRefreshDemo/Resources/badge_dark.png)](https://yalantis.com/?utm_source=github) 6 | 7 | [Live DEMO on appetize.io](https://appetize.io/app/ed1av4rv6k3f29y8e07kpj3e9g?device=nexus5&scale=75&orientation=portrait&osVersion=7.0) 8 | 9 | Android app on Google Play 10 | 11 | Check this [project on dribbble](https://dribbble.com/shots/2904577-Multi-Selection-Experiment) 12 | 13 | Read how we did it [on our blog](https://yalantis.com/blog/how-we-created-a-multiselection-solution-for-android/) 14 | 15 | 16 | 17 | ##Requirements 18 | - Android SDK 17+ 19 | 20 | ##Usage 21 | 22 | Add to your root build.gradle: 23 | ```Groovy 24 | allprojects { 25 | repositories { 26 | ... 27 | maven { url "https://jitpack.io" } 28 | } 29 | } 30 | ``` 31 | 32 | Add the dependency: 33 | ```Groovy 34 | dependencies { 35 | compile 'com.github.yalantis:multi-selection:v0.1' 36 | } 37 | ``` 38 | 39 | ## How to use this library 40 | 41 | Instructions can be found [here](https://yalantis.com/blog/how-we-created-a-multiselection-solution-for-android/) in section How to use MultiSelect 42 | 43 | ## Let us know! 44 | 45 | We’d be really happy if you sent us links to your projects where you use our component. Just send an email to github@yalantis.com And do let us know if you have any questions or suggestion regarding the animation. 46 | 47 | P.S. We’re going to publish more awesomeness wrapped in code and a tutorial on how to make UI for iOS (Android) better than better. Stay tuned! 48 | 49 | ## License 50 | 51 | The MIT License (MIT), Yalantis, https://yalantis.com 52 | Please see the LICENSE file 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/yalantis/multiselectdemo/demo/model/TrackList.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselectdemo.demo.model; 2 | 3 | import com.yalantis.multiselectdemo.R; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by Artem Kholodnyi on 9/6/16. 10 | */ 11 | public class TrackList { 12 | public final static List TRACKS = new ArrayList(){{ 13 | add(new Track("Dead Inside", "Muse", R.drawable.img_dead)); 14 | add(new Track("Sandman", "Hurts", R.drawable.img_sandman)); 15 | add(new Track("Doing It to Death", "The Kills", R.drawable.img_doing_it)); 16 | add(new Track("Born This Way", "Thousand Foot Krunch", R.drawable.img_born)); 17 | add(new Track("Light Up the Sky", "Thousand Foot Krunch", R.drawable.img_light_up)); 18 | add(new Track("Rolling Stone", "Hurts", R.drawable.img_rolling)); 19 | add(new Track("Hater", "Korn", R.drawable.img_hater)); 20 | add(new Track("Siberian Knights", "The Kills", R.drawable.img_siberian)); 21 | add(new Track("Hard As a Rock", "AC/DC", R.drawable.img_hard)); 22 | add(new Track("Hells Bells", "AC/DC", R.drawable.img_hells)); 23 | add(new Track("Way Down We Go", "Kaleo", R.drawable.img_way_down)); 24 | add(new Track("Psycho", "Muse", R.drawable.img_psycho)); 25 | add(new Track("Obstacles", "Syd Matters", R.drawable.img_obstacles)); 26 | add(new Track("Guilty All the Same", "Linking Park", R.drawable.img_guilty)); 27 | add(new Track("Jesus Walks", "Kanye West", R.drawable.img_jesus)); 28 | add(new Track("Bad Blood", "Black Pistol Fire", R.drawable.img_bad)); 29 | add(new Track("Black Skinhead", "Kanye West", R.drawable.img_blackskinhead)); 30 | add(new Track("Someone to Love Me", "Diddy", R.drawable.img_someone)); 31 | add(new Track("The Fire", "The Roots", R.drawable.img_thefire)); 32 | add(new Track("Shook Ones, Pt II", "Mobb Deep", R.drawable.img_shook)); 33 | add(new Track("We Don't Care", "Kanye West", R.drawable.img_jesus)); 34 | }}; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/MultiSelectBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib 2 | 3 | import android.content.Context 4 | import android.support.annotation.LayoutRes 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.ViewGroup 7 | import com.yalantis.multiselection.R 8 | import com.yalantis.multiselection.lib.adapter.BaseLeftAdapter 9 | import com.yalantis.multiselection.lib.adapter.BaseRightAdapter 10 | 11 | /** 12 | * Created by Artem Kholodnyi on 9/17/16. 13 | */ 14 | class MultiSelectBuilder>(val clazz: Class) { 15 | 16 | private lateinit var context: Context 17 | private lateinit var mountPoint: ViewGroup 18 | private lateinit var multiSelectView: MultiSelectImpl 19 | private lateinit var leftAdapter: BaseLeftAdapter 20 | private lateinit var rightAdapter: BaseRightAdapter 21 | private var sidebarWidth: Float = 0f 22 | 23 | fun withContext(context: Context): MultiSelectBuilder { 24 | this.context = context 25 | return this 26 | } 27 | 28 | fun mountOn(mountPoint: ViewGroup): MultiSelectBuilder { 29 | this.mountPoint = mountPoint 30 | return this 31 | } 32 | 33 | 34 | fun withSidebarWidth(sidebarWidthDp: Float): MultiSelectBuilder { 35 | this.sidebarWidth = sidebarWidthDp 36 | return this 37 | } 38 | 39 | fun withLeftAdapter(adapter: BaseLeftAdapter): MultiSelectBuilder { 40 | this.leftAdapter = adapter 41 | return this 42 | } 43 | 44 | 45 | fun withRightAdapter(adapter: BaseRightAdapter): MultiSelectBuilder { 46 | this.rightAdapter = adapter 47 | return this 48 | } 49 | 50 | fun build(): MultiSelect { 51 | this.multiSelectView = MultiSelectImpl(context, mountPoint) 52 | this.multiSelectView.id = R.id.yal_ms_multiselect 53 | this.multiSelectView.setSidebarWidthDp(this.sidebarWidth) 54 | this.multiSelectView.leftAdapter = this.leftAdapter 55 | this.multiSelectView.rightAdapter = this.rightAdapter 56 | this.mountPoint.addView(this.multiSelectView) 57 | return this.multiSelectView 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/yalantis/multiselectdemo/demo/DemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselectdemo.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.Snackbar; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.MenuItem; 8 | import android.view.ViewGroup; 9 | 10 | import com.yalantis.multiselectdemo.R; 11 | import com.yalantis.multiselectdemo.demo.model.Track; 12 | import com.yalantis.multiselectdemo.demo.model.TrackList; 13 | import com.yalantis.multiselection.lib.MultiSelectBuilder; 14 | import com.yalantis.multiselection.lib.MultiSelect; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * Created by Artem Kholodnyi on 9/3/16. 20 | */ 21 | public class DemoActivity extends AppCompatActivity { 22 | 23 | private MultiSelect mMultiSelect; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_main); 29 | setUpToolbar((Toolbar) findViewById(R.id.toolbar)); 30 | 31 | MultiSelectBuilder builder = new MultiSelectBuilder<>(Track.class) 32 | .withContext(this) 33 | .mountOn((ViewGroup) findViewById(R.id.mount_point)) 34 | .withSidebarWidth(46 + 8 * 2); // ImageView width with paddings 35 | 36 | setUpAdapters(builder); 37 | mMultiSelect = builder.build(); 38 | 39 | setUpDecoration(); 40 | } 41 | 42 | private void setUpDecoration() { 43 | TracksItemDecorator itemDecorator = new TracksItemDecorator( 44 | getResources().getDimensionPixelSize(R.dimen.decoration_size)); 45 | mMultiSelect.getRecyclerLeft().addItemDecoration(itemDecorator); 46 | mMultiSelect.getRecyclerRight().addItemDecoration(itemDecorator); 47 | } 48 | 49 | private void setUpAdapters(MultiSelectBuilder builder) { 50 | LeftAdapter leftAdapter = new LeftAdapter(position -> mMultiSelect.select(position)); 51 | RightAdapter rightAdapter = new RightAdapter(position -> mMultiSelect.deselect(position)); 52 | 53 | leftAdapter.addAll(TrackList.TRACKS); 54 | 55 | builder.withLeftAdapter(leftAdapter) 56 | .withRightAdapter(rightAdapter); 57 | } 58 | 59 | private void setUpToolbar(final Toolbar toolbar) { 60 | toolbar.inflateMenu(R.menu.menu); 61 | toolbar.setNavigationIcon(R.drawable.ic_back); 62 | toolbar.setOnMenuItemClickListener(item -> { 63 | if (item.getItemId() == R.id.select) { 64 | List items = mMultiSelect.getSelectedItems(); 65 | final int selectedCount = items.size(); 66 | final String msg; 67 | if (selectedCount == 0) { 68 | msg = getString(R.string.nothing_selected); 69 | mMultiSelect.showNotSelectedPage(); 70 | } else { 71 | msg = getResources().getQuantityString(R.plurals.you_selected_x_songs, 72 | selectedCount, selectedCount); 73 | mMultiSelect.showSelectedPage(); 74 | } 75 | Snackbar.make(toolbar, msg, Snackbar.LENGTH_LONG).show(); 76 | return true; 77 | } else { 78 | return false; 79 | } 80 | }); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | sidebarWidthDp=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$sidebarWidthDp`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$sidebarWidthDp`="\"$arg\"" 136 | fi 137 | sidebarWidthDp=$((sidebarWidthDp+1)) 138 | done 139 | case $sidebarWidthDp in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/MultiSelectImpl.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib 2 | 3 | import android.animation.Animator 4 | import android.animation.ValueAnimator 5 | import android.content.Context 6 | import android.os.Bundle 7 | import android.os.Parcelable 8 | import android.support.annotation.NonNull 9 | import android.support.v7.widget.LinearLayoutManager 10 | import android.support.v7.widget.RecyclerView 11 | import android.util.DisplayMetrics 12 | import android.util.Rational 13 | import android.view.LayoutInflater 14 | import android.view.View 15 | import android.view.ViewGroup 16 | import android.view.WindowManager 17 | import android.view.animation.OvershootInterpolator 18 | import android.widget.FrameLayout 19 | import com.yalantis.multiselection.R 20 | import com.yalantis.multiselection.lib.adapter.BaseAdapter 21 | import com.yalantis.multiselection.lib.adapter.BaseLeftAdapter 22 | import com.yalantis.multiselection.lib.adapter.BaseRightAdapter 23 | import com.yalantis.multiselection.lib.adapter.ViewPagerAdapter 24 | import com.yalantis.multiselection.lib.util.getLocationOnScreen 25 | import com.yalantis.multiselection.lib.util.removeFromParent 26 | import java.io.Serializable 27 | 28 | /** 29 | * Created by Artem Kholodnyi on 8/21/16. 30 | */ 31 | 32 | internal class MultiSelectImpl>(myContext: Context, 33 | val parent: ViewGroup) 34 | : FrameLayout(myContext), MultiSelect { 35 | 36 | init { 37 | isSaveEnabled = true 38 | } 39 | 40 | 41 | private val STATE_SUPER = "state super" 42 | private val STATE_SELECTED = "state selected" 43 | private val STATE_LEFT_POS = "state left pos" 44 | private val STATE_RIGHT_POS = "state right pos" 45 | 46 | private val location = intArrayOf(0, 0) 47 | 48 | private var pageWidth = 0f 49 | 50 | 51 | override val selectedItems: List? 52 | @NonNull 53 | get() { 54 | @Suppress("UNCHECKED_CAST") 55 | return (recyclerRight.adapter as BaseRightAdapter).items 56 | } 57 | 58 | override val recyclerLeft: RecyclerView by lazy { 59 | pagesAdapter.pageLeft.findViewById(R.id.yal_ms_recycler) as RecyclerView 60 | } 61 | 62 | override val recyclerRight: RecyclerView by lazy { 63 | pagesAdapter.pageRight.findViewById(R.id.yal_ms_recycler) as RecyclerView 64 | } 65 | 66 | override var leftAdapter: BaseLeftAdapter? 67 | get() { 68 | @Suppress("UNCHECKED_CAST") 69 | return recyclerLeft.adapter as BaseLeftAdapter? 70 | } 71 | set(value) { 72 | recyclerLeft.adapter = value 73 | } 74 | 75 | override var rightAdapter: BaseRightAdapter? 76 | get() { 77 | @Suppress("UNCHECKED_CAST") 78 | return recyclerRight.adapter as BaseRightAdapter? 79 | } 80 | set(value) { 81 | recyclerRight.adapter = value 82 | } 83 | 84 | private val viewPager: MultiSelectViewPager 85 | private lateinit var pagesAdapter: ViewPagerAdapter 86 | 87 | init { 88 | val view = LayoutInflater.from(context).inflate(R.layout.yal_ms_multiselect, parent, true) 89 | viewPager = view.findViewById(R.id.view_pager) as MultiSelectViewPager 90 | } 91 | 92 | override fun setSidebarWidthDp(iconWidthDp: Float) { 93 | val pageWidth = calcPageWidth(iconWidthDp) 94 | pagesAdapter = ViewPagerAdapter(pageWidth = pageWidth) 95 | viewPager.adapter = pagesAdapter 96 | viewPager.onClickCallback = { x, y -> 97 | pagesAdapter.pageRight.getLocationInWindow(location) 98 | 99 | when { 100 | viewPager.currentItem == 0 && x > location[0] -> { 101 | // left pane is opened and clicked on the right one 102 | viewPager.currentItem = 1 103 | true 104 | } 105 | viewPager.currentItem == 1 && x < location[0] -> { 106 | // right pane is opened and clicked on the left one 107 | viewPager.currentItem = 0 108 | 109 | true 110 | } 111 | else -> false 112 | } 113 | } 114 | setUpViews() 115 | } 116 | 117 | /** 118 | * @param sidebarWidthDp 119 | * @return page width in [0; 1] 120 | */ 121 | private fun calcPageWidth(sidebarWidthDp: Float): Float { 122 | val metrics = DisplayMetrics() 123 | val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager 124 | windowManager.defaultDisplay.getMetrics(metrics) 125 | 126 | val density = context.resources.displayMetrics.density 127 | val dpWidth = metrics.widthPixels / density 128 | 129 | return 1f - sidebarWidthDp / dpWidth 130 | } 131 | 132 | private fun setUpViews() { 133 | val inflater = LayoutInflater.from(context) 134 | 135 | pagesAdapter.pageLeft = inflater.inflate(R.layout.yal_ms_page_left, viewPager, false).apply { 136 | (findViewById(R.id.yal_ms_recycler) as RecyclerView).apply { 137 | layoutManager = LinearLayoutManager(context) 138 | itemAnimator = MultiSelectItemAnimator() 139 | } 140 | } 141 | 142 | pagesAdapter.pageRight = inflater.inflate(R.layout.yal_ms_page_right, viewPager, false).apply { 143 | (findViewById(R.id.yal_ms_recycler) as RecyclerView).apply { 144 | layoutManager = LinearLayoutManager(context) 145 | itemAnimator = MultiSelectItemAnimator() 146 | } 147 | } 148 | } 149 | 150 | override fun select(position: Int) = animate(recyclerLeft, recyclerRight, position) 151 | 152 | override fun deselect(position: Int) = animate(recyclerRight, recyclerLeft, position) 153 | 154 | private fun animate(sourceRecycler: RecyclerView, targetRecycler: RecyclerView, position: Int) { 155 | val view = sourceRecycler.layoutManager.findViewByPosition(position) ?: return 156 | 157 | view.isClickable = false 158 | 159 | val initial = view.getLocationOnScreen() 160 | 161 | sourceRecycler.layoutManager.removeViewAt(position) 162 | 163 | @Suppress("UNCHECKED_CAST") 164 | val removedItem: I = (sourceRecycler.adapter as BaseAdapter).removeItemAt(position) 165 | 166 | val width = view.width 167 | view.removeFromParent() 168 | parent.addView(view) 169 | view.layoutParams = view.layoutParams.apply { 170 | this.width = width 171 | } 172 | 173 | val container = sourceRecycler.getLocationOnScreen() 174 | 175 | view.translationX = (initial[0].toFloat()) 176 | view.translationY = (initial[1] - container[1]).toFloat() 177 | 178 | @Suppress("UNCHECKED_CAST") 179 | val newPos = (targetRecycler.adapter as BaseAdapter).add(removedItem, hide = true) 180 | val targetCoordinates = getTarget(targetRecycler, newPos) 181 | 182 | 183 | val targetX = (targetCoordinates[0] - initial[0]).toFloat() 184 | val targetY = (targetCoordinates[1] - initial[1]).toFloat() 185 | val duration = calcDuration(targetX, targetY) 186 | animateAlpha(removedItem, targetRecycler, view, duration) 187 | animateTranslation(view, deltaX = targetX, deltaY = targetY, duration = duration) 188 | } 189 | 190 | private fun calcDuration(targetX: Float, targetY: Float): Long { 191 | return Math.sqrt((targetX * targetX + targetY * targetY).toDouble()).times(0.7f).toLong() 192 | } 193 | 194 | private fun getTarget(targetRecycler: RecyclerView, index: Int): IntArray { 195 | val prev = Math.max(0, index - 0) 196 | var targetView: View? = targetRecycler.findViewHolderForAdapterPosition(prev)?.itemView 197 | if (targetView == null) { 198 | targetView = targetRecycler.findViewHolderForAdapterPosition(prev - 1)?.itemView 199 | if (targetView != null) { 200 | val targetCoordinates = targetView.getLocationOnScreen() 201 | targetCoordinates[1] += targetView.height 202 | return targetCoordinates 203 | } 204 | } 205 | 206 | if (targetView == null) { 207 | val targetCoordinates = targetRecycler.getLocationOnScreen() 208 | if (targetRecycler.childCount != 0) { 209 | // target view is not visible because recycler view is filled 210 | targetCoordinates[1] += targetRecycler.height 211 | } 212 | return targetCoordinates 213 | } 214 | 215 | return intArrayOf(0, 0) 216 | } 217 | 218 | override fun showSelectedPage() { 219 | viewPager.currentItem = 1 220 | } 221 | 222 | override fun showNotSelectedPage() { 223 | viewPager.currentItem = 0 224 | } 225 | 226 | internal fun animateAlpha(removedItem: I, targetRecycler: RecyclerView, view: View, duration: Long) { 227 | ValueAnimator.ofFloat(1f, 0f).setDuration(duration).apply { 228 | addUpdateListener { 229 | val value = it.animatedValue as Float 230 | if (view is ViewGroup) { 231 | (0..view.childCount - 1) 232 | .map { view.getChildAt(it) } 233 | .filter { it.id != R.id.yal_ms_avatar } 234 | .forEach { it?.alpha = value } 235 | } 236 | } 237 | 238 | addListener(object : Animator.AnimatorListener { 239 | override fun onAnimationStart(p0: Animator?) = Unit 240 | override fun onAnimationRepeat(p0: Animator?) = Unit 241 | override fun onAnimationEnd(p0: Animator?) = finallyDo() 242 | override fun onAnimationCancel(p0: Animator?) = finallyDo() 243 | 244 | val finallyDo = { 245 | view.removeFromParent() 246 | @Suppress("UNCHECKED_CAST") 247 | (targetRecycler.adapter as BaseAdapter).showItem(removedItem) 248 | Unit 249 | } 250 | }) 251 | }.start() 252 | } 253 | 254 | internal fun animateTranslation(view: View, deltaX: Float, deltaY: Float, duration: Long) { 255 | view.animate().setDuration(duration) 256 | .setInterpolator(OvershootInterpolator(1.1f)) 257 | .translationXBy(deltaX) 258 | .translationYBy(deltaY) 259 | .start() 260 | } 261 | 262 | override fun onSaveInstanceState(): Parcelable { 263 | val superState = super.onSaveInstanceState() 264 | return Bundle().apply { 265 | putParcelable(STATE_SUPER, superState) 266 | 267 | val selected = selectedItems 268 | if (selected is Serializable) { 269 | putSerializable(STATE_SELECTED, selected) 270 | } 271 | 272 | (recyclerLeft.layoutManager as? LinearLayoutManager)?.let { 273 | putInt(STATE_LEFT_POS, it.findFirstCompletelyVisibleItemPosition()) 274 | } 275 | 276 | (recyclerRight.layoutManager as? LinearLayoutManager)?.let { 277 | putInt(STATE_RIGHT_POS, it.findFirstCompletelyVisibleItemPosition()) 278 | } 279 | } 280 | } 281 | 282 | override fun onRestoreInstanceState(state: Parcelable?) { 283 | if (state is Bundle) { 284 | super.onRestoreInstanceState(state.getParcelable(STATE_SUPER)) 285 | 286 | @Suppress("UNCHECKED_CAST") 287 | val selected = state.getSerializable(STATE_SELECTED) as? List 288 | if (selected != null) { 289 | restoreState(selected) 290 | } 291 | 292 | val leftScrollPos = state.getInt(STATE_LEFT_POS, -1) 293 | if (leftScrollPos != -1) { 294 | recyclerLeft.layoutManager.scrollToPosition(leftScrollPos) 295 | } 296 | 297 | val rightScrollPos = state.getInt(STATE_RIGHT_POS, -1) 298 | if (rightScrollPos != -1) { 299 | recyclerRight.layoutManager.scrollToPosition(rightScrollPos) 300 | } 301 | } 302 | } 303 | 304 | private fun restoreState(selectedItems: List) { 305 | selectedItems.forEach { 306 | val index = leftAdapter?.items?.indexOf(it) 307 | if (index != null && index > -1) { 308 | leftAdapter?.removeItemAt(index) 309 | rightAdapter?.add(it, hide = false) 310 | } 311 | } 312 | } 313 | 314 | } -------------------------------------------------------------------------------- /multiselection/src/main/java/com/yalantis/multiselection/lib/MultiSelectItemAnimator.kt: -------------------------------------------------------------------------------- 1 | package com.yalantis.multiselection.lib 2 | 3 | 4 | import android.support.v4.animation.AnimatorCompatHelper 5 | import android.support.v4.view.ViewCompat 6 | import android.support.v4.view.ViewPropertyAnimatorListener 7 | import android.support.v7.widget.RecyclerView 8 | import android.support.v7.widget.RecyclerView.ViewHolder 9 | import android.support.v7.widget.SimpleItemAnimator 10 | import android.util.Log 11 | import android.view.View 12 | import java.util.* 13 | 14 | /** 15 | * This implementation of [RecyclerView.ItemAnimator] provides basic 16 | * animations on remove, add, and move events that happen to the items in 17 | * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. 18 | 19 | * @see RecyclerView.setItemAnimator 20 | */ 21 | class MultiSelectItemAnimator : SimpleItemAnimator() { 22 | 23 | override fun isRunning(): Boolean { 24 | return !mPendingAdditions.isEmpty() || 25 | !mPendingChanges.isEmpty() || 26 | !mPendingMoves.isEmpty() || 27 | !mPendingRemovals.isEmpty() || 28 | !mMoveAnimations.isEmpty() || 29 | !mRemoveAnimations.isEmpty() || 30 | !mAddAnimations.isEmpty() || 31 | !mChangeAnimations.isEmpty() || 32 | !mMovesList.isEmpty() || 33 | !mAdditionsList.isEmpty() || 34 | !mChangesList.isEmpty() 35 | } 36 | 37 | private val mPendingRemovals = mutableListOf() 38 | private val mPendingAdditions = mutableListOf() 39 | private val mPendingMoves = mutableListOf() 40 | private val mPendingChanges = mutableListOf() 41 | 42 | private val mAdditionsList = mutableListOf>() 43 | private val mMovesList = mutableListOf>() 44 | private val mChangesList = mutableListOf>() 45 | 46 | private val mAddAnimations = mutableListOf() 47 | private val mMoveAnimations = mutableListOf() 48 | private val mRemoveAnimations = mutableListOf() 49 | private val mChangeAnimations = mutableListOf() 50 | 51 | data class MoveInfo(var holder: ViewHolder, var fromX: Int, var fromY: Int, var toX: Int, var toY: Int) 52 | 53 | data class ChangeInfo(var oldHolder: ViewHolder?, var newHolder: ViewHolder?, 54 | var fromX: Int = 0, 55 | var fromY: Int = 0, 56 | var toX: Int = 0, 57 | var toY: Int = 0) 58 | 59 | 60 | override fun runPendingAnimations() { 61 | val removalsPending = !mPendingRemovals.isEmpty() 62 | val movesPending = !mPendingMoves.isEmpty() 63 | val changesPending = !mPendingChanges.isEmpty() 64 | val additionsPending = !mPendingAdditions.isEmpty() 65 | if (!removalsPending && !movesPending && !additionsPending && !changesPending) { 66 | // nothing to animate 67 | return 68 | } 69 | // First, remove stuff 70 | for (holder in mPendingRemovals) { 71 | animateRemoveImpl(holder) 72 | } 73 | mPendingRemovals.clear() 74 | // Next, move stuff 75 | if (movesPending) { 76 | val moves = ArrayList() 77 | moves.addAll(mPendingMoves) 78 | mMovesList.add(moves) 79 | mPendingMoves.clear() 80 | val mover = Runnable { 81 | for (moveInfo in moves) { 82 | animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, 83 | moveInfo.toX, moveInfo.toY) 84 | } 85 | moves.clear() 86 | mMovesList.remove(moves) 87 | } 88 | if (removalsPending) { 89 | val view = moves[0].holder.itemView 90 | ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()) 91 | } else { 92 | mover.run() 93 | } 94 | } 95 | // Next, change stuff, to run in parallel with move animations 96 | if (changesPending) { 97 | val changes = ArrayList() 98 | changes.addAll(mPendingChanges) 99 | mChangesList.add(changes) 100 | mPendingChanges.clear() 101 | val changer = Runnable { 102 | for (change in changes) { 103 | animateChangeImpl(change) 104 | } 105 | changes.clear() 106 | mChangesList.remove(changes) 107 | } 108 | if (removalsPending) { 109 | val holder = changes[0].oldHolder 110 | ViewCompat.postOnAnimationDelayed(holder!!.itemView, changer, getRemoveDuration()) 111 | } else { 112 | changer.run() 113 | } 114 | } 115 | // Next, add stuff 116 | if (additionsPending) { 117 | val additions = ArrayList() 118 | additions.addAll(mPendingAdditions) 119 | mAdditionsList.add(additions) 120 | mPendingAdditions.clear() 121 | val adder = Runnable { 122 | for (holder in additions) { 123 | animateAddImpl(holder) 124 | } 125 | additions.clear() 126 | mAdditionsList.remove(additions) 127 | } 128 | if (removalsPending || movesPending || changesPending) { 129 | val removeDuration = (if (removalsPending) getRemoveDuration() else 0).toLong() 130 | val moveDuration = (if (movesPending) getMoveDuration() else 0).toLong() 131 | val changeDuration = (if (changesPending) getChangeDuration() else 0).toLong() 132 | val totalDelay = removeDuration + Math.max(moveDuration, changeDuration) 133 | val view = additions[0].itemView 134 | ViewCompat.postOnAnimationDelayed(view, adder, totalDelay) 135 | } else { 136 | adder.run() 137 | } 138 | } 139 | } 140 | 141 | override fun animateRemove(holder: ViewHolder): Boolean { 142 | resetAnimation(holder) 143 | mPendingRemovals.add(holder) 144 | return true 145 | } 146 | 147 | private fun animateRemoveImpl(holder: ViewHolder) { 148 | val view = holder.itemView 149 | val animation = ViewCompat.animate(view) 150 | mRemoveAnimations.add(holder) 151 | animation.setDuration(getRemoveDuration()).alpha(0f).setListener(object : VpaListenerAdapter() { 152 | override fun onAnimationStart(view: View) { 153 | dispatchRemoveStarting(holder) 154 | } 155 | 156 | override fun onAnimationEnd(view: View) { 157 | animation.setListener(null) 158 | ViewCompat.setAlpha(view, 1f) 159 | dispatchRemoveFinished(holder) 160 | mRemoveAnimations.remove(holder) 161 | dispatchFinishedWhenDone() 162 | } 163 | }).start() 164 | } 165 | 166 | override fun animateAdd(holder: ViewHolder): Boolean { 167 | Log.d(">>>", holder.toString()) 168 | resetAnimation(holder) 169 | ViewCompat.setAlpha(holder.itemView, 0f) 170 | mPendingAdditions.add(holder) 171 | return true 172 | } 173 | 174 | private fun animateAddImpl(holder: ViewHolder) { 175 | val view = holder.itemView 176 | val animation = ViewCompat.animate(view) 177 | mAddAnimations.add(holder) 178 | animation.alpha(1f).setDuration(addDuration).setListener(object : VpaListenerAdapter() { 179 | override fun onAnimationStart(view: View) { 180 | dispatchAddStarting(holder) 181 | } 182 | 183 | override fun onAnimationCancel(view: View) { 184 | ViewCompat.setAlpha(view, 1f) 185 | } 186 | 187 | override fun onAnimationEnd(view: View) { 188 | if (view.parent is RecyclerView) { 189 | animation.setListener(null) 190 | dispatchAddFinished(holder) 191 | mAddAnimations.remove(holder) 192 | dispatchFinishedWhenDone() 193 | } 194 | } 195 | }).start() 196 | } 197 | 198 | override fun animateMove(holder: ViewHolder, fromX: Int, fromY: Int, 199 | toX: Int, toY: Int): Boolean { 200 | var fromX = fromX 201 | var fromY = fromY 202 | val view = holder.itemView 203 | fromX += ViewCompat.getTranslationX(holder.itemView).toInt() 204 | fromY += ViewCompat.getTranslationY(holder.itemView).toInt() 205 | resetAnimation(holder) 206 | val deltaX = toX - fromX 207 | val deltaY = toY - fromY 208 | if (deltaX == 0 && deltaY == 0) { 209 | dispatchMoveFinished(holder) 210 | return false 211 | } 212 | if (deltaX != 0) { 213 | ViewCompat.setTranslationX(view, (-deltaX).toFloat()) 214 | } 215 | if (deltaY != 0) { 216 | ViewCompat.setTranslationY(view, (-deltaY).toFloat()) 217 | } 218 | mPendingMoves += MoveInfo(holder, fromX, fromY, toX, toY) 219 | return true 220 | } 221 | 222 | private fun animateMoveImpl(holder: ViewHolder, fromX: Int, fromY: Int, toX: Int, toY: Int) { 223 | val view = holder.itemView 224 | val deltaX = toX - fromX 225 | val deltaY = toY - fromY 226 | if (deltaX != 0) { 227 | ViewCompat.animate(view).translationX(0f) 228 | } 229 | if (deltaY != 0) { 230 | ViewCompat.animate(view).translationY(0f) 231 | } 232 | // TODO: make EndActions end listeners instead, since end actions aren't called when 233 | // vpas are canceled (and can't end them. why?) 234 | // need listener functionality in VPACompat for this. Ick. 235 | val animation = ViewCompat.animate(view) 236 | mMoveAnimations.add(holder) 237 | animation.setDuration(getMoveDuration()).setListener(object : VpaListenerAdapter() { 238 | override fun onAnimationStart(view: View) { 239 | dispatchMoveStarting(holder) 240 | } 241 | 242 | override fun onAnimationCancel(view: View) { 243 | if (deltaX != 0) { 244 | ViewCompat.setTranslationX(view, 0f) 245 | } 246 | if (deltaY != 0) { 247 | ViewCompat.setTranslationY(view, 0f) 248 | } 249 | } 250 | 251 | override fun onAnimationEnd(view: View) { 252 | if (view.parent is RecyclerView) { 253 | animation.setListener(null) 254 | dispatchMoveFinished(holder) 255 | mMoveAnimations.remove(holder) 256 | dispatchFinishedWhenDone() 257 | } 258 | } 259 | }).start() 260 | } 261 | 262 | override fun animateChange(oldHolder: ViewHolder, newHolder: ViewHolder?, 263 | fromX: Int, fromY: Int, toX: Int, toY: Int): Boolean { 264 | if (oldHolder === newHolder) { 265 | // Don't know how to run change animations when the same view holder is re-used. 266 | // run a move animation to handle position changes. 267 | return animateMove(oldHolder, fromX, fromY, toX, toY) 268 | } 269 | val prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView) 270 | val prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView) 271 | val prevAlpha = ViewCompat.getAlpha(oldHolder.itemView) 272 | resetAnimation(oldHolder) 273 | val deltaX = (toX.toFloat() - fromX.toFloat() - prevTranslationX).toInt() 274 | val deltaY = (toY.toFloat() - fromY.toFloat() - prevTranslationY).toInt() 275 | // recover prev translation state after ending animation 276 | ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX) 277 | ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY) 278 | ViewCompat.setAlpha(oldHolder.itemView, prevAlpha) 279 | if (newHolder != null) { 280 | // carry over translation values 281 | resetAnimation(newHolder) 282 | ViewCompat.setTranslationX(newHolder.itemView, (-deltaX).toFloat()) 283 | ViewCompat.setTranslationY(newHolder.itemView, (-deltaY).toFloat()) 284 | ViewCompat.setAlpha(newHolder.itemView, 0f) 285 | } 286 | mPendingChanges += ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY) 287 | return true 288 | } 289 | 290 | private fun animateChangeImpl(changeInfo: ChangeInfo) { 291 | val holder = changeInfo.oldHolder 292 | val view = holder?.itemView 293 | val newHolder = changeInfo.newHolder 294 | val newView = newHolder?.itemView 295 | if (view != null) { 296 | val oldViewAnim = ViewCompat.animate(view).setDuration( 297 | changeDuration) 298 | mChangeAnimations.add(changeInfo.oldHolder!!) 299 | oldViewAnim.translationX((changeInfo.toX - changeInfo.fromX).toFloat()) 300 | oldViewAnim.translationY((changeInfo.toY - changeInfo.fromY).toFloat()) 301 | oldViewAnim.alpha(0f).setListener(object : VpaListenerAdapter() { 302 | override fun onAnimationStart(view: View) { 303 | dispatchChangeStarting(changeInfo.oldHolder, true) 304 | } 305 | 306 | override fun onAnimationEnd(view: View) { 307 | oldViewAnim.setListener(null) 308 | ViewCompat.setAlpha(view, 1f) 309 | ViewCompat.setTranslationX(view, 0f) 310 | ViewCompat.setTranslationY(view, 0f) 311 | dispatchChangeFinished(changeInfo.oldHolder, true) 312 | mChangeAnimations.remove(changeInfo.oldHolder!!) 313 | dispatchFinishedWhenDone() 314 | } 315 | }).start() 316 | } 317 | if (newView != null) { 318 | val newViewAnimation = ViewCompat.animate(newView) 319 | mChangeAnimations.add(changeInfo.newHolder!!) 320 | newViewAnimation.translationX(0f).translationY(0f).setDuration(getChangeDuration()).alpha(1f).setListener(object : VpaListenerAdapter() { 321 | override fun onAnimationStart(view: View) { 322 | dispatchChangeStarting(changeInfo.newHolder, false) 323 | } 324 | 325 | override fun onAnimationEnd(view: View) { 326 | newViewAnimation.setListener(null) 327 | ViewCompat.setAlpha(newView, 1f) 328 | ViewCompat.setTranslationX(newView, 0f) 329 | ViewCompat.setTranslationY(newView, 0f) 330 | dispatchChangeFinished(changeInfo.newHolder, false) 331 | mChangeAnimations.remove(changeInfo.newHolder!!) 332 | dispatchFinishedWhenDone() 333 | } 334 | }).start() 335 | } 336 | } 337 | 338 | private fun endChangeAnimation(infoList: MutableList, item: ViewHolder) { 339 | for (i in infoList.indices.reversed()) { 340 | val changeInfo = infoList[i] 341 | if (endChangeAnimationIfNecessary(changeInfo, item)) { 342 | if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { 343 | infoList.remove(changeInfo) 344 | } 345 | } 346 | } 347 | } 348 | 349 | private fun endChangeAnimationIfNecessary(changeInfo: ChangeInfo) { 350 | if (changeInfo.oldHolder != null) { 351 | endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder!!) 352 | } 353 | if (changeInfo.newHolder != null) { 354 | endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder!!) 355 | } 356 | } 357 | 358 | private fun endChangeAnimationIfNecessary(changeInfo: ChangeInfo, item: ViewHolder): Boolean { 359 | var oldItem = false 360 | if (changeInfo.newHolder === item) { 361 | changeInfo.newHolder = null 362 | } else if (changeInfo.oldHolder === item) { 363 | changeInfo.oldHolder = null 364 | oldItem = true 365 | } else { 366 | return false 367 | } 368 | ViewCompat.setAlpha(item.itemView, 1f) 369 | ViewCompat.setTranslationX(item.itemView, 0f) 370 | ViewCompat.setTranslationY(item.itemView, 0f) 371 | dispatchChangeFinished(item, oldItem) 372 | return true 373 | } 374 | 375 | override fun endAnimation(item: ViewHolder) { 376 | val view = item.itemView 377 | // this will trigger end callback which should set properties to their target values. 378 | ViewCompat.animate(view).cancel() 379 | // TODO if some other animations are chained to end, how do we cancel them as well? 380 | for (i in mPendingMoves.indices.reversed()) { 381 | val moveInfo = mPendingMoves[i] 382 | if (moveInfo.holder === item) { 383 | ViewCompat.setTranslationY(view, 0f) 384 | ViewCompat.setTranslationX(view, 0f) 385 | dispatchMoveFinished(item) 386 | mPendingMoves.removeAt(i) 387 | } 388 | } 389 | endChangeAnimation(mPendingChanges, item) 390 | if (mPendingRemovals.remove(item)) { 391 | ViewCompat.setAlpha(view, 1f) 392 | dispatchRemoveFinished(item) 393 | } 394 | if (mPendingAdditions.remove(item)) { 395 | ViewCompat.setAlpha(view, 1f) 396 | dispatchAddFinished(item) 397 | } 398 | 399 | for (i in mChangesList.indices.reversed()) { 400 | val changes = mChangesList[i] 401 | endChangeAnimation(changes, item) 402 | if (changes.isEmpty()) { 403 | mChangesList.removeAt(i) 404 | } 405 | } 406 | for (i in mMovesList.indices.reversed()) { 407 | val moves = mMovesList[i] 408 | for (j in moves.indices.reversed()) { 409 | val moveInfo = moves[j] 410 | if (moveInfo.holder === item) { 411 | ViewCompat.setTranslationY(view, 0f) 412 | ViewCompat.setTranslationX(view, 0f) 413 | dispatchMoveFinished(item) 414 | moves.removeAt(j) 415 | if (moves.isEmpty()) { 416 | mMovesList.removeAt(i) 417 | } 418 | break 419 | } 420 | } 421 | } 422 | for (i in mAdditionsList.indices.reversed()) { 423 | val additions = mAdditionsList[i] 424 | if (additions.remove(item)) { 425 | ViewCompat.setAlpha(view, 1f) 426 | dispatchAddFinished(item) 427 | if (additions.isEmpty()) { 428 | mAdditionsList.removeAt(i) 429 | } 430 | } 431 | } 432 | 433 | // animations should be ended by the cancel above. 434 | //noinspection PointlessBooleanExpression,ConstantConditions 435 | if (mRemoveAnimations.remove(item) && DEBUG) { 436 | throw IllegalStateException("after animation is cancelled, item should not be in " + "mRemoveAnimations list") 437 | } 438 | 439 | //noinspection PointlessBooleanExpression,ConstantConditions 440 | if (mAddAnimations.remove(item) && DEBUG) { 441 | throw IllegalStateException("after animation is cancelled, item should not be in " + "mAddAnimations list") 442 | } 443 | 444 | //noinspection PointlessBooleanExpression,ConstantConditions 445 | if (mChangeAnimations.remove(item) && DEBUG) { 446 | throw IllegalStateException("after animation is cancelled, item should not be in " + "mChangeAnimations list") 447 | } 448 | 449 | //noinspection PointlessBooleanExpression,ConstantConditions 450 | if (mMoveAnimations.remove(item) && DEBUG) { 451 | throw IllegalStateException("after animation is cancelled, item should not be in " + "mMoveAnimations list") 452 | } 453 | dispatchFinishedWhenDone() 454 | } 455 | 456 | private fun resetAnimation(holder: ViewHolder) { 457 | AnimatorCompatHelper.clearInterpolator(holder.itemView) 458 | endAnimation(holder) 459 | } 460 | 461 | 462 | /** 463 | * Check the state of currently pending and running animations. If there are none 464 | * pending/running, call [.dispatchAnimationsFinished] to notify any 465 | * listeners. 466 | */ 467 | private fun dispatchFinishedWhenDone() { 468 | if (!isRunning) { 469 | dispatchAnimationsFinished() 470 | } 471 | } 472 | 473 | override fun endAnimations() { 474 | var count = mPendingMoves.size 475 | for (i in count - 1 downTo 0) { 476 | val item = mPendingMoves[i] 477 | val view = item.holder.itemView 478 | ViewCompat.setTranslationY(view, 0f) 479 | ViewCompat.setTranslationX(view, 0f) 480 | dispatchMoveFinished(item.holder) 481 | mPendingMoves.removeAt(i) 482 | } 483 | count = mPendingRemovals.size 484 | for (i in count - 1 downTo 0) { 485 | val item = mPendingRemovals[i] 486 | dispatchRemoveFinished(item) 487 | mPendingRemovals.removeAt(i) 488 | } 489 | count = mPendingAdditions.size 490 | for (i in count - 1 downTo 0) { 491 | val item = mPendingAdditions[i] 492 | val view = item.itemView 493 | ViewCompat.setAlpha(view, 1f) 494 | dispatchAddFinished(item) 495 | mPendingAdditions.removeAt(i) 496 | } 497 | count = mPendingChanges.size 498 | for (i in count - 1 downTo 0) { 499 | endChangeAnimationIfNecessary(mPendingChanges[i]) 500 | } 501 | mPendingChanges.clear() 502 | if (!isRunning) { 503 | return 504 | } 505 | 506 | var listCount = mMovesList.size 507 | for (i in listCount - 1 downTo 0) { 508 | val moves = mMovesList[i] 509 | count = moves.size 510 | for (j in count - 1 downTo 0) { 511 | val moveInfo = moves[j] 512 | val item = moveInfo.holder 513 | val view = item.itemView 514 | ViewCompat.setTranslationY(view, 0f) 515 | ViewCompat.setTranslationX(view, 0f) 516 | dispatchMoveFinished(moveInfo.holder) 517 | moves.removeAt(j) 518 | if (moves.isEmpty()) { 519 | mMovesList.remove(moves) 520 | } 521 | } 522 | } 523 | listCount = mAdditionsList.size 524 | for (i in listCount - 1 downTo 0) { 525 | val additions = mAdditionsList[i] 526 | count = additions.size 527 | for (j in count - 1 downTo 0) { 528 | val item = additions[j] 529 | val view = item.itemView 530 | ViewCompat.setAlpha(view, 1f) 531 | dispatchAddFinished(item) 532 | additions.removeAt(j) 533 | if (additions.isEmpty()) { 534 | mAdditionsList.remove(additions) 535 | } 536 | } 537 | } 538 | listCount = mChangesList.size 539 | for (i in listCount - 1 downTo 0) { 540 | val changes = mChangesList[i] 541 | count = changes.size 542 | for (j in count - 1 downTo 0) { 543 | endChangeAnimationIfNecessary(changes[j]) 544 | if (changes.isEmpty()) { 545 | mChangesList.remove(changes) 546 | } 547 | } 548 | } 549 | 550 | cancelAll(mRemoveAnimations) 551 | cancelAll(mMoveAnimations) 552 | cancelAll(mAddAnimations) 553 | cancelAll(mChangeAnimations) 554 | 555 | dispatchAnimationsFinished() 556 | } 557 | 558 | internal fun cancelAll(viewHolders: List) { 559 | for (i in viewHolders.indices.reversed()) { 560 | ViewCompat.animate(viewHolders[i].itemView).cancel() 561 | } 562 | } 563 | 564 | /** 565 | * {@inheritDoc} 566 | * 567 | * 568 | * If the payload list is not empty, DefaultItemAnimator returns `true`. 569 | * When this is the case: 570 | * 571 | * * If you override [.animateChange], both 572 | * ViewHolder arguments will be the same instance. 573 | * 574 | * * 575 | * If you are not overriding [.animateChange], 576 | * then DefaultItemAnimator will call [.animateMove] and 577 | * run a move animation instead. 578 | * 579 | * 580 | */ 581 | override fun canReuseUpdatedViewHolder(viewHolder: ViewHolder, 582 | payloads: List): Boolean { 583 | return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads) 584 | } 585 | 586 | private open class VpaListenerAdapter : ViewPropertyAnimatorListener { 587 | override fun onAnimationStart(view: View) { 588 | } 589 | 590 | override fun onAnimationEnd(view: View) { 591 | } 592 | 593 | override fun onAnimationCancel(view: View) { 594 | } 595 | } 596 | 597 | companion object { 598 | private val DEBUG = false 599 | } 600 | } 601 | --------------------------------------------------------------------------------