diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml deleted file mode 100644 index 9cb5a3f..0000000 --- a/.github/workflows/dev.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Dev - -on: - push: - paths-ignore: - - '**/*.md' - - '**/*.txt' - branches: - - 'dev' - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - name: checkout - uses: actions/checkout@v2 - - name: setup JDK 11 - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '11' - - name: Install Android SDK - uses: malinskiy/action-android/install-sdk@release/0.1.2 - - name: Run unit tests - run: ./gradlew testDebugUnitTest --stacktrace - - name: Assemble library - run: ./gradlew assembleRelease diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index fd63099..dd6a975 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -3,27 +3,30 @@ name: Release on: push: paths-ignore: - - '**/*.md' - - '**/*.txt' + - '**.md' + - '**.MD' branches: - 'master' jobs: build: - runs-on: ubuntu-latest steps: - - name: checkout - uses: actions/checkout@v2 - - name: setup JDK 11 - uses: actions/setup-java@v3 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: - distribution: 'zulu' + distribution: temurin java-version: '11' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Install Android SDK - uses: malinskiy/action-android/install-sdk@release/0.1.2 + uses: malinskiy/action-android/install-sdk@release/0.1.4 + - name: Run unit tests run: ./gradlew testDebugUnitTest --stacktrace + - name: Assemble library run: ./gradlew assembleRelease diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index c7f01eb..0f50c2b 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -1,28 +1,36 @@ -name: Pull Requests +name: Pull requests on: pull_request: paths-ignore: - - '**/*.md' - - '**/*.txt' + - '**.md' + - '**.MD' branches: - - 'dev' + - 'master' + +concurrency: + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: build: - runs-on: ubuntu-latest steps: - - name: checkout - uses: actions/checkout@v2 - - name: setup JDK 1.8 - uses: actions/setup-java@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: - java-version: 1.8 + distribution: temurin + java-version: '11' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Install Android SDK - uses: malinskiy/action-android/install-sdk@release/0.1.0 + uses: malinskiy/action-android/install-sdk@release/0.1.4 + - name: Run unit tests - run: ./gradlew testDebugUnitTest + run: ./gradlew testDebugUnitTest --stacktrace + - name: Assemble library run: ./gradlew assembleRelease diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d36f4d..7b71fd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +# 2.1.0 + +- Do not split decoration size in LinearMarginDecoration: [#27](https://github.com/rubensousa/Decorator/issues/27) +- Fixed decorations not being applied for items removed: [#28](https://github.com/rubensousa/Decorator/issues/28) +- **BREAKING CHANGE**: DecorationLookup now passes the current ViewHolder [#29](https://github.com/rubensousa/Decorator/issues/29): + +Before: + +```kotlin +interface DecorationLookup { + fun shouldApplyDecoration(position: Int, itemCount: Int): Boolean +} +``` + +After: + +```kotlin +interface DecorationLookup { + fun shouldApplyDecoration(viewHolder: RecyclerView.ViewHolder, itemCount: Int): Boolean +} +``` + # 2.0.2 - Moved `setDecorationLookup` to `AbstractMarginDecoration` diff --git a/README.md b/README.md index 62c3109..325932c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Decorator is a library that helps creating composable margins and dividers in Re ## Install ```groovy -implementation 'com.rubensousa:decorator:2.0.2' +implementation 'com.rubensousa:decorator:2.1.0' ``` ## How to use @@ -175,12 +175,10 @@ You can also decide if an item at a given position should have a decoration appl ```kotlin decoration.setDecorationLookup(object : DecorationLookup { - // Don't apply the decoration on position 4 - override fun shouldApplyDecoration(position: Int, itemCount: Int): Boolean { - return position != 4 + override fun shouldApplyDecoration(viewHolder: RecyclerView.ViewHolder, itemCount: Int): Boolean { + return viewHolder.layoutPosition != 4 } - }) ``` diff --git a/build.gradle b/build.gradle index bd2f8a1..712342d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.6.21' + ext.kotlin_version = '1.7.10' ext.nav_version = '2.4.1' repositories { @@ -8,10 +8,9 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" - classpath "org.jetbrains.dokka:dokka-gradle-plugin:$kotlin_version" } } diff --git a/decorator/build.gradle b/decorator/build.gradle index 9c62ebd..75e6146 100644 --- a/decorator/build.gradle +++ b/decorator/build.gradle @@ -1,6 +1,9 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'org.jetbrains.dokka' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +apply from: 'publish.gradle' android { compileSdkVersion 32 @@ -18,17 +21,19 @@ android { } } + publishing { + singleVariant('release') { + withSourcesJar() + withJavadocJar() + } + } + } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" api "androidx.recyclerview:recyclerview:1.2.1" - implementation 'androidx.appcompat:appcompat:1.4.2' - implementation 'androidx.core:core-ktx:1.8.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } - - -apply from: 'publish.gradle' \ No newline at end of file diff --git a/decorator/gradle.properties b/decorator/gradle.properties index a3e9f00..27411d5 100644 --- a/decorator/gradle.properties +++ b/decorator/gradle.properties @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -LIBRARY_VERSION=2.0.2 +LIBRARY_VERSION=2.1.0 LIBRARY_GROUP=com.rubensousa LIBRARY_ARTIFACT=decorator # POM info diff --git a/decorator/publish.gradle b/decorator/publish.gradle index 7c01404..ffa54d3 100644 --- a/decorator/publish.gradle +++ b/decorator/publish.gradle @@ -14,38 +14,6 @@ * limitations under the License. */ -/* - * BSD 3-Clause License - * - * Copyright (c) 2019, Sky Italia - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - /* * Copyright 2020 RĂºben Sousa * @@ -93,27 +61,9 @@ * POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt * POM_LICENCE_DIST=repo */ - -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21" - } -} - apply plugin: 'maven-publish' apply plugin: 'signing' -def isDokkaAvailable = project.tasks.findByName('dokkaJavadoc') - -// Check if the project has dokka setup before trying to apply the plugin -// If dokka isn't setup, javadocs won't be generated for kotlin files -if (isDokkaAvailable) { - apply plugin: 'org.jetbrains.dokka' -} - ext["signing.keyId"] = getSecretKeyId() ext["signing.password"] = getSecretKeyPassword() ext["signing.secretKeyRingFile"] = getSecretKeyRingFilePath() @@ -158,75 +108,7 @@ String getSonatypePassword() { } } - afterEvaluate { project -> - - def publishSources = shouldPublishSources() - def publishDocs = shouldPublishDocs() - - if (publishDocs) { - - // If dokka is setup, generate the javadocs for kotlin files - if (isDokkaAvailable) { - tasks.dokkaJavadoc.configure { - outputDirectory.set(file("$buildDir/javadoc")) - } - } - - task androidJavadocs(type: Javadoc) { - source = android.sourceSets.main.java.source - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - excludes = ['**/*.kt'] - } - - task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { - getArchiveClassifier().set('javadoc') - if (isDokkaAvailable) { - from dokkaJavadoc.outputDirectory - } else { - from androidJavadocs.destinationDir - } - } - - if (JavaVersion.current().isJava8Compatible()) { - allprojects { - tasks.withType(Javadoc) { - options.addStringOption('Xdoclint:none', '-quiet') - } - } - } - - if (JavaVersion.current().isJava9Compatible()) { - allprojects { - tasks.withType(Javadoc) { - options.addBooleanOption('html5', true) - } - } - } - - android.libraryVariants.all { variant -> - tasks.androidJavadocs.doFirst { - classpath += files(variant.javaCompileProvider.get().classpath.files - .join(File.pathSeparator)) - } - } - - artifacts { - archives androidJavadocsJar - } - } - - if (publishSources) { - task androidSourcesJar(type: Jar) { - getArchiveClassifier().set('sources') - from android.sourceSets.main.java.source - } - - artifacts { - archives androidSourcesJar - } - } - publishing { publications { release(MavenPublication) { @@ -234,12 +116,6 @@ afterEvaluate { project -> groupId LIBRARY_GROUP version LIBRARY_VERSION artifactId LIBRARY_ARTIFACT - if (publishSources) { - artifact androidSourcesJar - } - if (publishDocs) { - artifact androidJavadocsJar - } configurePom(pom) } } @@ -257,22 +133,6 @@ afterEvaluate { project -> } } -def shouldPublishSources() { - if (hasProperty("LIBRARY_PUBLISH_SOURCES")) { - return LIBRARY_PUBLISH_SOURCES == "true" - } else { - return true - } -} - -def shouldPublishDocs() { - if (hasProperty("LIBRARY_PUBLISH_DOCS")) { - return LIBRARY_PUBLISH_DOCS == "true" - } else { - return true - } -} - def configurePom(pom) { pom.name = POM_NAME pom.packaging = POM_PACKAGING @@ -305,4 +165,4 @@ version LIBRARY_VERSION signing { sign publishing.publications -} \ No newline at end of file +} diff --git a/decorator/src/main/java/com/rubensousa/decorator/AbstractMarginDecoration.kt b/decorator/src/main/java/com/rubensousa/decorator/AbstractMarginDecoration.kt index bea988a..e68d9bc 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/AbstractMarginDecoration.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/AbstractMarginDecoration.kt @@ -19,6 +19,7 @@ package com.rubensousa.decorator import android.graphics.Rect import android.view.View import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder /** * A base [RecyclerView.ItemDecoration] that checks if item offsets should be applied @@ -33,11 +34,18 @@ abstract class AbstractMarginDecoration(private var decorationLookup: Decoration parent: RecyclerView, state: RecyclerView.State ) { - val lm = parent.layoutManager as RecyclerView.LayoutManager val layoutParams = view.layoutParams as RecyclerView.LayoutParams - val position = layoutParams.absoluteAdapterPosition - if (shouldApplyDecorationAt(position, lm.itemCount)) { - getItemOffsets(outRect, view, position, parent, state, lm) + + /** + * We need to use the layout position since it's the only valid + * source of truth at this stage. + * The item could've been removed and adapter position + * in that case is set to RecyclerView.NO_POSITION + */ + val position = layoutParams.viewLayoutPosition + val viewHolder = parent.getChildViewHolder(view) + if (shouldApplyDecorationAt(viewHolder, state.itemCount)) { + getItemOffsets(outRect, view, position, parent, state) } } @@ -50,15 +58,11 @@ abstract class AbstractMarginDecoration(private var decorationLookup: Decoration } /** - * @return true if decoration will be applied for [position] - * or false if position is not valid - * or [decorationLookup] doesn't allow decoration for this [position] + * @return true if decoration will be applied for [viewHolder] + * or false if[decorationLookup] doesn't allow decoration for this [viewHolder] */ - fun shouldApplyDecorationAt(position: Int, itemCount: Int): Boolean { - if (position == RecyclerView.NO_POSITION) { - return false - } - return decorationLookup?.shouldApplyDecoration(position, itemCount) ?: true + fun shouldApplyDecorationAt(viewHolder: ViewHolder, itemCount: Int): Boolean { + return decorationLookup?.shouldApplyDecoration(viewHolder, itemCount) ?: true } abstract fun getItemOffsets( @@ -66,8 +70,7 @@ abstract class AbstractMarginDecoration(private var decorationLookup: Decoration view: View, position: Int, parent: RecyclerView, - state: RecyclerView.State, - layoutManager: RecyclerView.LayoutManager + state: RecyclerView.State ) } diff --git a/decorator/src/main/java/com/rubensousa/decorator/DecorationLookup.kt b/decorator/src/main/java/com/rubensousa/decorator/DecorationLookup.kt index af2f39e..86e4353 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/DecorationLookup.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/DecorationLookup.kt @@ -16,14 +16,20 @@ package com.rubensousa.decorator +import androidx.recyclerview.widget.RecyclerView + /** - * Checks if a decoration should be applied for a given adapter position + * Checks if a decoration should be applied for a given [RecyclerView.ViewHolder] */ interface DecorationLookup { /** - * @return true if the item at [position] should have decoration applied to + * @param viewHolder the ViewHolder currently in layout + * + * @param itemCount the item count at the layout stage. See [RecyclerView.State.getItemCount] + * + * @return true if the ViewHolder should have decoration applied to */ - fun shouldApplyDecoration(position: Int, itemCount: Int): Boolean + fun shouldApplyDecoration(viewHolder: RecyclerView.ViewHolder, itemCount: Int): Boolean } diff --git a/decorator/src/main/java/com/rubensousa/decorator/GridBoundsMarginDecoration.kt b/decorator/src/main/java/com/rubensousa/decorator/GridBoundsMarginDecoration.kt index 430f30a..697ce65 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/GridBoundsMarginDecoration.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/GridBoundsMarginDecoration.kt @@ -21,6 +21,7 @@ import android.view.View import androidx.annotation.Px import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import kotlin.math.ceil /** * A [RecyclerView.ItemDecoration] that applies a margin to the bounds of the RecyclerView @@ -199,12 +200,11 @@ class GridBoundsMarginDecoration( view: View, position: Int, parent: RecyclerView, - state: RecyclerView.State, - layoutManager: RecyclerView.LayoutManager + state: RecyclerView.State ) { val columns = columnProvider.getNumberOfColumns() val columnIndex = position.rem(columns) - val lines = Math.ceil(layoutManager.itemCount / columns.toDouble()).toInt() + val lines = ceil(state.itemCount / columns.toDouble()).toInt() val lineIndex = position / columns if (orientation == RecyclerView.VERTICAL) { diff --git a/decorator/src/main/java/com/rubensousa/decorator/GridDividerDecoration.kt b/decorator/src/main/java/com/rubensousa/decorator/GridDividerDecoration.kt index e212bb5..22fb19d 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/GridDividerDecoration.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/GridDividerDecoration.kt @@ -130,31 +130,30 @@ class GridDividerDecoration( view: View, position: Int, parent: RecyclerView, - state: RecyclerView.State, - layoutManager: RecyclerView.LayoutManager + state: RecyclerView.State ) { val columns = columnProvider.getNumberOfColumns() - val itemCount = layoutManager.itemCount if (orientation == RecyclerView.VERTICAL) { - applyVerticalOffsets(outRect, position, columns, itemCount) + applyVerticalOffsets(outRect, position, columns, state.itemCount) } else { - applyHorizontalOffsets(outRect, position, columns, itemCount) + applyHorizontalOffsets(outRect, position, columns, state.itemCount) } } - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - super.onDraw(c, parent, state) - val layoutManager = parent.layoutManager ?: return + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) val columns = columnProvider.getNumberOfColumns() - val itemCount = layoutManager.itemCount + val itemCount = state.itemCount for (i in 0 until parent.childCount) { val child = parent.getChildAt(i) - val adapterPosition = parent.getChildAdapterPosition(child) - if (shouldApplyDecorationAt(adapterPosition, itemCount)) { + val layoutParams = child.layoutParams as RecyclerView.LayoutParams + val position = layoutParams.viewLayoutPosition + val viewHolder = parent.getChildViewHolder(child) + if (shouldApplyDecorationAt(viewHolder, itemCount)) { if (orientation == RecyclerView.VERTICAL) { - drawVertical(c, child, adapterPosition, itemCount, columns) + drawVertical(canvas, child, position, itemCount, columns, parent) } else { - drawHorizontal(c, child, adapterPosition, itemCount, columns) + drawHorizontal(canvas, child, position, itemCount, columns, parent) } } } @@ -278,32 +277,40 @@ class GridDividerDecoration( view: View, position: Int, itemCount: Int, - columns: Int + columns: Int, + recyclerView: RecyclerView ) { val bottomPosition = if (!inverted) { getBottomPosition(position, columns, itemCount) } else { getTopPosition(position, columns) } - if (bottomPosition != null && shouldApplyDecorationAt(bottomPosition, itemCount)) { - canvas.drawRect( - view.left.toFloat() + heightMargin, - view.bottom.toFloat() + widthMargin, - view.right.toFloat() - heightMargin, - view.bottom.toFloat() + size + widthMargin, - paint - ) + + if (bottomPosition != null) { + val viewHolder = recyclerView.findViewHolderForLayoutPosition(bottomPosition) + if (viewHolder != null && shouldApplyDecorationAt(viewHolder, itemCount)) { + canvas.drawRect( + view.left.toFloat() + heightMargin, + view.bottom.toFloat() + widthMargin, + view.right.toFloat() - heightMargin, + view.bottom.toFloat() + size + widthMargin, + paint + ) + } } val rightPosition = getRightPosition(position, columns, itemCount) - if (rightPosition != null && shouldApplyDecorationAt(rightPosition, itemCount)) { - canvas.drawRect( - view.right.toFloat() + widthMargin, - view.top.toFloat() + heightMargin, - view.right.toFloat() + widthMargin + size, - view.bottom.toFloat() - heightMargin, - paint - ) + if (rightPosition != null) { + val viewHolder = recyclerView.findViewHolderForLayoutPosition(rightPosition) + if (viewHolder != null && shouldApplyDecorationAt(viewHolder, itemCount)) { + canvas.drawRect( + view.right.toFloat() + widthMargin, + view.top.toFloat() + heightMargin, + view.right.toFloat() + widthMargin + size, + view.bottom.toFloat() - heightMargin, + paint + ) + } } } @@ -312,32 +319,39 @@ class GridDividerDecoration( view: View, position: Int, itemCount: Int, - columns: Int + columns: Int, + recyclerView: RecyclerView ) { val bottomPosition = if (!inverted) { getBottomPosition(position, columns, itemCount) } else { getTopPosition(position, columns) } - if (bottomPosition != null && shouldApplyDecorationAt(bottomPosition, itemCount)) { - canvas.drawRect( - view.right.toFloat() + widthMargin, - view.top.toFloat() + heightMargin, - view.right.toFloat() + widthMargin + size, - view.bottom.toFloat() - heightMargin, - paint - ) + if (bottomPosition != null) { + val viewHolder = recyclerView.findViewHolderForLayoutPosition(bottomPosition) + if (viewHolder != null && shouldApplyDecorationAt(viewHolder, itemCount)) { + canvas.drawRect( + view.right.toFloat() + widthMargin, + view.top.toFloat() + heightMargin, + view.right.toFloat() + widthMargin + size, + view.bottom.toFloat() - heightMargin, + paint + ) + } } val rightPosition = getRightPosition(position, columns, itemCount) - if (rightPosition != null && shouldApplyDecorationAt(rightPosition, itemCount)) { - canvas.drawRect( - view.left.toFloat() + heightMargin, - view.bottom.toFloat() + widthMargin, - view.right.toFloat() - heightMargin, - view.bottom.toFloat() + widthMargin + size, - paint - ) + if (rightPosition != null) { + val viewHolder = recyclerView.findViewHolderForLayoutPosition(rightPosition) + if (viewHolder != null && shouldApplyDecorationAt(viewHolder, itemCount)) { + canvas.drawRect( + view.left.toFloat() + heightMargin, + view.bottom.toFloat() + widthMargin, + view.right.toFloat() - heightMargin, + view.bottom.toFloat() + widthMargin + size, + paint + ) + } } } diff --git a/decorator/src/main/java/com/rubensousa/decorator/GridMarginDecoration.kt b/decorator/src/main/java/com/rubensousa/decorator/GridMarginDecoration.kt index 318cb6e..3799846 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/GridMarginDecoration.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/GridMarginDecoration.kt @@ -20,6 +20,7 @@ import android.graphics.Rect import android.view.View import androidx.annotation.Px import androidx.recyclerview.widget.RecyclerView +import kotlin.math.ceil /** * An item decoration that applies a fixed margin to all sides for a grid. @@ -165,13 +166,12 @@ class GridMarginDecoration( view: View, position: Int, parent: RecyclerView, - state: RecyclerView.State, - layoutManager: RecyclerView.LayoutManager + state: RecyclerView.State ) { val columns = columnProvider.getNumberOfColumns() val columnIndex = position.rem(columns) - val lines = Math.ceil(layoutManager.itemCount / columns.toDouble()).toInt() + val lines = ceil(state.itemCount / columns.toDouble()).toInt() val lineIndex = position / columns if (orientation == RecyclerView.VERTICAL) { diff --git a/decorator/src/main/java/com/rubensousa/decorator/GridSpanBoundsMarginDecoration.kt b/decorator/src/main/java/com/rubensousa/decorator/GridSpanBoundsMarginDecoration.kt index 9c5639f..14c6cc6 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/GridSpanBoundsMarginDecoration.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/GridSpanBoundsMarginDecoration.kt @@ -83,8 +83,7 @@ class GridSpanBoundsMarginDecoration( view: View, position: Int, parent: RecyclerView, - state: RecyclerView.State, - layoutManager: RecyclerView.LayoutManager + state: RecyclerView.State ) { val layoutParams = view.layoutParams as GridLayoutManager.LayoutParams val columnIndex = layoutParams.spanIndex @@ -93,7 +92,7 @@ class GridSpanBoundsMarginDecoration( return } - val itemCount = layoutManager.itemCount + val itemCount = state.itemCount val columns = gridLayoutManager.spanCount if (gridLayoutManager.orientation == RecyclerView.VERTICAL) { diff --git a/decorator/src/main/java/com/rubensousa/decorator/GridSpanMarginDecoration.kt b/decorator/src/main/java/com/rubensousa/decorator/GridSpanMarginDecoration.kt index e3e5b3f..b88f778 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/GridSpanMarginDecoration.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/GridSpanMarginDecoration.kt @@ -122,8 +122,7 @@ class GridSpanMarginDecoration( view: View, position: Int, parent: RecyclerView, - state: RecyclerView.State, - layoutManager: RecyclerView.LayoutManager + state: RecyclerView.State ) { val layoutParams = view.layoutParams as GridLayoutManager.LayoutParams val columnIndex = layoutParams.spanIndex @@ -132,7 +131,7 @@ class GridSpanMarginDecoration( return } - val itemCount = layoutManager.itemCount + val itemCount = state.itemCount val columns = gridLayoutManager.spanCount if (gridLayoutManager.orientation == RecyclerView.VERTICAL) { diff --git a/decorator/src/main/java/com/rubensousa/decorator/LinearBoundsMarginDecoration.kt b/decorator/src/main/java/com/rubensousa/decorator/LinearBoundsMarginDecoration.kt index f7e41ec..04eefab 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/LinearBoundsMarginDecoration.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/LinearBoundsMarginDecoration.kt @@ -160,14 +160,12 @@ class LinearBoundsMarginDecoration( view: View, position: Int, parent: RecyclerView, - state: RecyclerView.State, - layoutManager: RecyclerView.LayoutManager + state: RecyclerView.State ) { - val itemCount = layoutManager.itemCount if (orientation == RecyclerView.VERTICAL) { - applyVerticalOffsets(outRect, position, itemCount) + applyVerticalOffsets(outRect, position, state.itemCount) } else { - applyHorizontalOffsets(outRect, position, itemCount) + applyHorizontalOffsets(outRect, position, state.itemCount) } } diff --git a/decorator/src/main/java/com/rubensousa/decorator/LinearDividerDecoration.kt b/decorator/src/main/java/com/rubensousa/decorator/LinearDividerDecoration.kt index c2119c2..5b4ba4d 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/LinearDividerDecoration.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/LinearDividerDecoration.kt @@ -180,29 +180,29 @@ class LinearDividerDecoration( view: View, position: Int, parent: RecyclerView, - state: RecyclerView.State, - layoutManager: RecyclerView.LayoutManager + state: RecyclerView.State ) { - val itemCount = layoutManager.itemCount if (orientation == RecyclerView.VERTICAL) { - applyVerticalOffsets(outRect, position, itemCount) + applyVerticalOffsets(outRect, position, state.itemCount) } else { - applyHorizontalOffsets(outRect, position, itemCount) + applyHorizontalOffsets(outRect, position, state.itemCount) } } - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - super.onDraw(c, parent, state) + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) val layoutManager = parent.layoutManager ?: return val itemCount = layoutManager.itemCount for (i in 0 until parent.childCount) { val child = parent.getChildAt(i) - val adapterPosition = parent.getChildAdapterPosition(child) - if (shouldApplyDecorationAt(adapterPosition, itemCount)) { + val layoutParams = child.layoutParams as RecyclerView.LayoutParams + val position = layoutParams.viewLayoutPosition + val viewHolder = parent.getChildViewHolder(child) + if (shouldApplyDecorationAt(viewHolder, itemCount)) { if (orientation == RecyclerView.VERTICAL) { - drawVertical(c, child, adapterPosition, itemCount, layoutManager) + drawVertical(canvas, child, position, itemCount, layoutManager) } else { - drawHorizontal(c, child, adapterPosition, itemCount, layoutManager) + drawHorizontal(canvas, child, position, itemCount, layoutManager) } } } diff --git a/decorator/src/main/java/com/rubensousa/decorator/LinearMarginDecoration.kt b/decorator/src/main/java/com/rubensousa/decorator/LinearMarginDecoration.kt index e7939da..013425e 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/LinearMarginDecoration.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/LinearMarginDecoration.kt @@ -198,45 +198,44 @@ class LinearMarginDecoration( view: View, position: Int, parent: RecyclerView, - state: RecyclerView.State, - layoutManager: RecyclerView.LayoutManager + state: RecyclerView.State ) { - val itemCount = layoutManager.itemCount if (orientation == RecyclerView.VERTICAL) { - applyVerticalOffsets(outRect, position, itemCount) + applyVerticalOffsets(outRect, position, state.itemCount) } else { - applyHorizontalOffsets(outRect, position, itemCount) + applyHorizontalOffsets(outRect, position, state.itemCount) } } private fun applyVerticalOffsets(outRect: Rect, position: Int, itemCount: Int) { if (position == 0) { if (!inverted) { - if (position == itemCount - 1) { + if (itemCount - 1 == 0) { outRect.bottom = getEdgeMargin(addAfterLastPosition, bottomMargin) } else { - outRect.bottom = bottomMargin / 2 + outRect.bottom = bottomMargin } outRect.top = getEdgeMargin(addBeforeFirstPosition, topMargin) } else { - if (position == itemCount - 1) { + if (itemCount - 1 == 0) { outRect.top = getEdgeMargin(addAfterLastPosition, topMargin) } else { - outRect.top = topMargin / 2 + outRect.top = topMargin } outRect.bottom = getEdgeMargin(addBeforeFirstPosition, bottomMargin) } } else if (position == itemCount - 1) { if (!inverted) { - outRect.top = topMargin / 2 outRect.bottom = getEdgeMargin(addAfterLastPosition, bottomMargin) } else { - outRect.bottom = bottomMargin / 2 outRect.top = getEdgeMargin(addAfterLastPosition, topMargin) } } else { - outRect.top = topMargin / 2 - outRect.bottom = bottomMargin / 2 + if (inverted) { + outRect.top = topMargin + } else { + outRect.bottom = bottomMargin + } } outRect.left = leftMargin outRect.right = rightMargin @@ -245,31 +244,32 @@ class LinearMarginDecoration( private fun applyHorizontalOffsets(outRect: Rect, position: Int, itemCount: Int) { if (position == 0) { if (!inverted) { - if (position == itemCount - 1) { + if (itemCount - 1 == 0) { outRect.right = getEdgeMargin(addAfterLastPosition, rightMargin) } else { - outRect.right = rightMargin / 2 + outRect.right = rightMargin } outRect.left = getEdgeMargin(addBeforeFirstPosition, leftMargin) } else { - if (position == itemCount - 1) { + if (itemCount - 1 == 0) { outRect.left = getEdgeMargin(addAfterLastPosition, leftMargin) } else { - outRect.left = leftMargin / 2 + outRect.left = leftMargin } outRect.right = getEdgeMargin(addBeforeFirstPosition, rightMargin) } } else if (position == itemCount - 1) { if (!inverted) { - outRect.left = leftMargin / 2 outRect.right = getEdgeMargin(addAfterLastPosition, rightMargin) } else { - outRect.right = rightMargin / 2 outRect.left = getEdgeMargin(addAfterLastPosition, leftMargin) } } else { - outRect.left = leftMargin / 2 - outRect.right = rightMargin / 2 + if (inverted) { + outRect.left = leftMargin + } else { + outRect.right = rightMargin + } } outRect.top = topMargin outRect.bottom = bottomMargin diff --git a/decorator/src/main/java/com/rubensousa/decorator/MergeDecorationLookup.kt b/decorator/src/main/java/com/rubensousa/decorator/MergeDecorationLookup.kt index a0dce2e..2580727 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/MergeDecorationLookup.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/MergeDecorationLookup.kt @@ -1,5 +1,7 @@ package com.rubensousa.decorator +import androidx.recyclerview.widget.RecyclerView + /** * Allows combining multiple [DecorationLookup] */ @@ -20,9 +22,12 @@ class MergeDecorationLookup private constructor(private val delegates: List - if (!delegate.shouldApplyDecoration(position, itemCount)) { + if (!delegate.shouldApplyDecoration(viewHolder, itemCount)) { return false } } diff --git a/decorator/src/main/java/com/rubensousa/decorator/SingleItemDecorationLookup.kt b/decorator/src/main/java/com/rubensousa/decorator/SingleItemDecorationLookup.kt index 007457a..3edbfe2 100644 --- a/decorator/src/main/java/com/rubensousa/decorator/SingleItemDecorationLookup.kt +++ b/decorator/src/main/java/com/rubensousa/decorator/SingleItemDecorationLookup.kt @@ -1,15 +1,20 @@ package com.rubensousa.decorator +import androidx.recyclerview.widget.RecyclerView + /** * A [DecorationLookup] that disables decoration when there's only a single item */ class SingleItemDecorationLookup : DecorationLookup { - override fun shouldApplyDecoration(position: Int, itemCount: Int): Boolean { - if (position == 0 && itemCount == 1) { + override fun shouldApplyDecoration( + viewHolder: RecyclerView.ViewHolder, + itemCount: Int + ): Boolean { + if (viewHolder.layoutPosition == 0 && itemCount == 1) { return false } return true } -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index 23339e0..83df5e3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,5 @@ org.gradle.jvmargs=-Xmx1536m # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 28f7f33..6bfadf4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 08 14:38:39 CEST 2020 +#Tue Nov 15 17:37:35 CET 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/sample/src/main/java/com/rubensousa/decorator/sample/DecorationListController.kt b/sample/src/main/java/com/rubensousa/decorator/sample/DecorationListController.kt index 3d88c27..44c7694 100644 --- a/sample/src/main/java/com/rubensousa/decorator/sample/DecorationListController.kt +++ b/sample/src/main/java/com/rubensousa/decorator/sample/DecorationListController.kt @@ -17,11 +17,19 @@ package com.rubensousa.decorator.sample import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.ConcatAdapter +import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.RecyclerView -import com.rubensousa.decorator.sample.R import com.rubensousa.decorator.sample.adapter.CardAdapter -import com.rubensousa.decorator.sample.decorations.* -import com.rubensousa.decorator.sample.model.CardModel +import com.rubensousa.decorator.sample.decorations.DecorationDelegate +import com.rubensousa.decorator.sample.decorations.GridBoundsDelegate +import com.rubensousa.decorator.sample.decorations.GridDividerDelegate +import com.rubensousa.decorator.sample.decorations.GridMarginDelegate +import com.rubensousa.decorator.sample.decorations.GridSpanBoundsDelegate +import com.rubensousa.decorator.sample.decorations.GridSpanDelegate +import com.rubensousa.decorator.sample.decorations.LinearBoundsDelegate +import com.rubensousa.decorator.sample.decorations.LinearDividerDelegate +import com.rubensousa.decorator.sample.decorations.LinearMarginDelegate class DecorationListController( private val fragment: Fragment, @@ -29,7 +37,6 @@ class DecorationListController( ) { private val decorations = mutableMapOf() - private var adapter = CardAdapter(R.layout.list_card) private var recyclerView: RecyclerView? = null init { @@ -61,15 +68,18 @@ class DecorationListController( fun setup(recyclerView: RecyclerView) { val delegate = getDelegate() + val itemAnimator = recyclerView.itemAnimator as DefaultItemAnimator + itemAnimator.removeDuration = 3000 recyclerView.layoutManager = delegate.createLayoutManager(fragment.requireActivity()) val decorations = delegate.getExtraDecorations() recyclerView.addItemDecoration(delegate.getDecoration()) decorations.forEach { decoration -> recyclerView.addItemDecoration(decoration) } + val adapter = CardAdapter(recyclerView, R.layout.list_card) recyclerView.adapter = adapter this.recyclerView = recyclerView - createItems() + adapter.setItems(getDelegate().getNumberOfItems()) } fun setInverted(inverted: Boolean) { @@ -96,10 +106,18 @@ class DecorationListController( R.layout.list_card_horizontal } - adapter = CardAdapter(layoutId) + val adapter = ConcatAdapter( + CardAdapter( + recyclerView!!, + layoutId + ).also { it.setItems(getDelegate().getNumberOfItems() / 2) }, + CardAdapter( + recyclerView!!, + layoutId + ).also { it.setItems(getDelegate().getNumberOfItems() / 2) }, + ) recyclerView?.layoutManager = delegate.createLayoutManager(fragment.requireActivity()) recyclerView?.adapter = adapter - createItems() recyclerView?.invalidateItemDecorations() } @@ -118,19 +136,6 @@ class DecorationListController( recyclerView?.invalidateItemDecorations() } - private fun createItems() { - val items = arrayListOf() - val numberOfItems = getDelegate().getNumberOfItems() - repeat(numberOfItems) { id -> - items.add(CardModel(id)) - } - submitList(items) - } - - private fun submitList(list: List) { - adapter.submitList(list) - } - fun onDestroyView() { recyclerView = null } diff --git a/sample/src/main/java/com/rubensousa/decorator/sample/MainListController.kt b/sample/src/main/java/com/rubensousa/decorator/sample/MainListController.kt index 6835468..b44b6a9 100644 --- a/sample/src/main/java/com/rubensousa/decorator/sample/MainListController.kt +++ b/sample/src/main/java/com/rubensousa/decorator/sample/MainListController.kt @@ -21,12 +21,16 @@ import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.rubensousa.decorator.* +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.rubensousa.decorator.DecorationLookup +import com.rubensousa.decorator.LinearBoundsMarginDecoration +import com.rubensousa.decorator.LinearDividerDecoration +import com.rubensousa.decorator.LinearMarginDecoration +import com.rubensousa.decorator.MergeDecorationLookup +import com.rubensousa.decorator.SingleItemDecorationLookup import com.rubensousa.decorator.sample.adapter.OptionAdapter import com.rubensousa.decorator.sample.extensions.dpToPx import com.rubensousa.decorator.sample.model.OptionModel -import com.rubensousa.decorator.sample.DecorationFragmentArgs -import com.rubensousa.decorator.sample.R class MainListController(private val fragment: Fragment) : OptionAdapter.OnOptionClickListener { @@ -72,9 +76,11 @@ class MainListController(private val fragment: Fragment) : topMargin = marginDecorationSize, bottomMargin = marginDecorationSize, decorationLookup = object : DecorationLookup { - // We can specify if we don't want to apply a decoration at a given position - override fun shouldApplyDecoration(position: Int, itemCount: Int): Boolean { - return position != 4 + // We can specify if we don't want to apply a decoration at a given ViewHolder + override fun shouldApplyDecoration( + viewHolder: ViewHolder, itemCount: Int + ): Boolean { + return viewHolder.layoutPosition != 4 } } ) @@ -93,8 +99,11 @@ class MainListController(private val fragment: Fragment) : // Disables decoration when there's only one item SingleItemDecorationLookup(), object : DecorationLookup { - override fun shouldApplyDecoration(position: Int, itemCount: Int): Boolean { - return position != 4 && position != 3 + override fun shouldApplyDecoration( + viewHolder: ViewHolder, itemCount: Int + ): Boolean { + val layoutPosition = viewHolder.layoutPosition + return layoutPosition != 4 && layoutPosition != 3 } }) ) diff --git a/sample/src/main/java/com/rubensousa/decorator/sample/adapter/CardAdapter.kt b/sample/src/main/java/com/rubensousa/decorator/sample/adapter/CardAdapter.kt index d38b92e..e664ea3 100644 --- a/sample/src/main/java/com/rubensousa/decorator/sample/adapter/CardAdapter.kt +++ b/sample/src/main/java/com/rubensousa/decorator/sample/adapter/CardAdapter.kt @@ -16,31 +16,20 @@ package com.rubensousa.decorator.sample.adapter +import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.rubensousa.decorator.sample.R import com.rubensousa.decorator.sample.model.CardModel -class CardAdapter(private val layoutId: Int = R.layout.list_card) : - ListAdapter(DIFF_CALLBACK) { +class CardAdapter( + private val recyclerView: RecyclerView, + private val layoutId: Int = R.layout.list_card +) : RecyclerView.Adapter() { - companion object { - private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: CardModel, newItem: CardModel): Boolean { - return oldItem.getId() == newItem.getId() - } - - override fun areContentsTheSame(oldItem: CardModel, newItem: CardModel): Boolean { - return oldItem.equals(newItem) - } - - } - } + private val items = ArrayList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { return VH( @@ -49,18 +38,35 @@ class CardAdapter(private val layoutId: Int = R.layout.list_card) : parent, false ) - ) + ).also { viewHolder -> + viewHolder.itemView.setOnClickListener { + val position = viewHolder.bindingAdapterPosition + if (position in 0 until itemCount) { + items.removeAt(position) + notifyItemRemoved(position) + // Make sure the item decorations are still applied + // after the remove animation is done + recyclerView.post { + recyclerView.itemAnimator?.isRunning { + recyclerView.invalidateItemDecorations() + } + } + } + } + } } - override fun onBindViewHolder(holder: VH, position: Int) { - holder.bind(getItem(position)) + @SuppressLint("NotifyDataSetChanged") + fun setItems(numberOfItems: Int) { + repeat(numberOfItems) { id -> + items.add(CardModel(id)) + } + notifyDataSetChanged() } - class VH(view: View) : RecyclerView.ViewHolder(view) { + override fun getItemCount(): Int = items.size - fun bind(item: CardModel) { + override fun onBindViewHolder(holder: VH, position: Int) {} - } - - } + class VH(view: View) : RecyclerView.ViewHolder(view) } \ No newline at end of file diff --git a/sample/src/main/java/com/rubensousa/decorator/sample/decorations/LinearMarginDelegate.kt b/sample/src/main/java/com/rubensousa/decorator/sample/decorations/LinearMarginDelegate.kt index 067d889..5df427f 100644 --- a/sample/src/main/java/com/rubensousa/decorator/sample/decorations/LinearMarginDelegate.kt +++ b/sample/src/main/java/com/rubensousa/decorator/sample/decorations/LinearMarginDelegate.kt @@ -26,8 +26,8 @@ import com.rubensousa.decorator.sample.extensions.dpToPx class LinearMarginDelegate(private val resources: Resources) : DecorationDelegate() { private var decoration = LinearMarginDecoration.create( - resources.dpToPx(getDefaultSizeDp()), - addBeforeFirstPosition = false, + margin = resources.dpToPx(getDefaultSizeDp()), + addBeforeFirstPosition = true, addAfterLastPosition = false )