├── core
├── .gitignore
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ └── values
│ │ │ ├── ids.xml
│ │ │ ├── public.xml
│ │ │ └── attrs.xml
│ │ └── kotlin
│ │ └── com
│ │ └── zedalpha
│ │ └── shadowgadgets
│ │ └── core
│ │ ├── ListUtils.kt
│ │ ├── shadow
│ │ ├── BaseView.kt
│ │ └── CoreShadow.kt
│ │ ├── layer
│ │ ├── ManagedLayer.kt
│ │ ├── LocationTracker.kt
│ │ ├── RenderNodeLayer.kt
│ │ ├── ViewLayer.kt
│ │ └── Layer.kt
│ │ ├── rendernode
│ │ ├── RenderNodeApi23.kt
│ │ ├── RenderNodeWrapper.kt
│ │ └── RenderNodeFactory.kt
│ │ ├── Shadow.kt
│ │ ├── ClippedShadow.kt
│ │ └── ColorUtils.kt
├── consumer-rules.pro
└── build.gradle.kts
├── demo
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── drawable
│ │ │ ├── bg_root.xml
│ │ │ ├── bg_rounded_rectangle.xml
│ │ │ ├── bg_drop_target.xml
│ │ │ ├── divider.xml
│ │ │ ├── bg_roundish_square.xml
│ │ │ ├── ic_clear.xml
│ │ │ ├── ic_add.xml
│ │ │ ├── ic_arrow_up.xml
│ │ │ ├── ic_arrow_back.xml
│ │ │ └── ic_arrow_forward.xml
│ │ ├── layout
│ │ │ ├── compose_view.xml
│ │ │ ├── internal_title_switcher.xml
│ │ │ ├── dialog_root.xml
│ │ │ ├── dialog_description.xml
│ │ │ ├── view_root.xml
│ │ │ ├── item_description.xml
│ │ │ ├── item_colorful.xml
│ │ │ ├── fragment_root.xml
│ │ │ ├── view_panel.xml
│ │ │ ├── fragment_compat_stress_test.xml
│ │ │ ├── dialog_welcome.xml
│ │ │ ├── activity_main.xml
│ │ │ └── fragment_apply.xml
│ │ ├── values
│ │ │ ├── dimens.xml
│ │ │ ├── colors.xml
│ │ │ └── themes.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── anim
│ │ │ ├── slide_in_top.xml
│ │ │ ├── slide_in_bottom.xml
│ │ │ ├── slide_out_bottom.xml
│ │ │ └── slide_out_top.xml
│ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ └── xml
│ │ │ └── motions.xml
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ └── com
│ │ └── zedalpha
│ │ └── shadowgadgets
│ │ └── demo
│ │ ├── internal
│ │ ├── ContentCardView.kt
│ │ ├── TitleSwitcher.kt
│ │ ├── SlantGridDrawable.kt
│ │ ├── Utils.kt
│ │ └── ControlView.kt
│ │ └── topic
│ │ ├── PlaneTopic.kt
│ │ ├── Topic.kt
│ │ ├── IrregularTopic.kt
│ │ ├── ApplyTopic.kt
│ │ └── compat
│ │ └── ViewIntroPanel.kt
└── build.gradle.kts
├── stubs
├── .gitignore
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── android
│ │ └── view
│ │ ├── HardwareCanvas.java
│ │ └── DisplayListCanvas.java
└── build.gradle.kts
├── view
├── .gitignore
├── lint
│ ├── .gitignore
│ ├── src
│ │ ├── main
│ │ │ ├── resources
│ │ │ │ └── META-INF
│ │ │ │ │ └── services
│ │ │ │ │ └── com.android.tools.lint.client.api.IssueRegistry
│ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── zedalpha
│ │ │ │ └── shadowgadgets
│ │ │ │ └── view
│ │ │ │ └── lint
│ │ │ │ ├── RelativeOverlapDetectorSG.kt
│ │ │ │ ├── MotionLayoutIdDetectorSG.kt
│ │ │ │ ├── UseCompoundDrawableDetectorSG.kt
│ │ │ │ ├── ChildCountDetectorSG.kt
│ │ │ │ ├── internal
│ │ │ │ ├── ElementWrapper.kt
│ │ │ │ ├── ContextWrappers.kt
│ │ │ │ ├── BaseDetector.kt
│ │ │ │ ├── LibraryConstants.kt
│ │ │ │ └── ConfigurationWrapper.kt
│ │ │ │ ├── ConstraintLayoutDetectorSG.kt
│ │ │ │ ├── MotionLayoutDetectorSG.kt
│ │ │ │ ├── ObjectAnimatorDetectorSG.kt
│ │ │ │ ├── ViewIssueRegistry.kt
│ │ │ │ ├── InefficientWeightDetectorSG.kt
│ │ │ │ ├── NestedScrollingWidgetDetectorSG.kt
│ │ │ │ ├── UselessViewDetectorSG.kt
│ │ │ │ └── WrongIdDetectorSG.kt
│ │ └── test
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── zedalpha
│ │ │ └── shadowgadgets
│ │ │ └── view
│ │ │ └── lint
│ │ │ ├── RelativeOverlapDetectorSGTest.kt
│ │ │ ├── UseCompoundDrawableDetectorSGTest.kt
│ │ │ ├── MotionLayoutIdDetectorSGTest.kt
│ │ │ ├── InefficientWeightDetectorSGTest.kt
│ │ │ ├── ShadowAttributesDetectorTest.kt
│ │ │ └── ConstraintLayoutDetectorSGTest.kt
│ └── build.gradle.kts
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ └── com
│ │ │ └── zedalpha
│ │ │ └── shadowgadgets
│ │ │ └── view
│ │ │ ├── shadow
│ │ │ ├── DrawPlane.kt
│ │ │ ├── NullShadow.kt
│ │ │ ├── SingleController.kt
│ │ │ ├── SoloController.kt
│ │ │ ├── GroupController.kt
│ │ │ ├── OverlayController.kt
│ │ │ └── GroupShadow.kt
│ │ │ ├── internal
│ │ │ ├── BaseDrawable.kt
│ │ │ ├── ShadowAttributes.kt
│ │ │ └── MaterialComponentsUtils.kt
│ │ │ ├── drawable
│ │ │ └── DrawableController.kt
│ │ │ ├── viewgroup
│ │ │ ├── RecyclingManager.kt
│ │ │ ├── ShadowsRecyclerView.kt
│ │ │ ├── ShadowsGridView.kt
│ │ │ ├── ShadowsListView.kt
│ │ │ ├── ShadowsStackView.kt
│ │ │ ├── ShadowsExpandableListView.kt
│ │ │ ├── ShadowsRadioGroup.kt
│ │ │ ├── ShadowsMotionLayout.kt
│ │ │ ├── ShadowsFrameLayout.kt
│ │ │ ├── ShadowsCoordinatorLayout.kt
│ │ │ ├── ShadowsLinearLayout.kt
│ │ │ ├── ShadowsRelativeLayout.kt
│ │ │ ├── ShadowsConstraintLayout.kt
│ │ │ ├── ShadowsChipGroup.kt
│ │ │ └── ShadowsMaterialButtonToggleGroup.kt
│ │ │ └── ShadowPlane.kt
│ │ └── res
│ │ └── values
│ │ ├── public.xml
│ │ ├── ids.xml
│ │ └── attrs.xml
├── build.gradle.kts
└── consumer-rules.pro
├── compose
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ └── com
│ │ └── zedalpha
│ │ └── shadowgadgets
│ │ └── compose
│ │ └── ExperimentalColorCompat.kt
└── build.gradle.kts
├── jitpack.yml
├── gradle
├── build-logic
│ ├── gradle.properties
│ ├── settings.gradle.kts
│ └── convention
│ │ ├── build.gradle.kts
│ │ └── src
│ │ └── main
│ │ └── kotlin
│ │ ├── AndroidLibraryConventionPlugin.kt
│ │ └── PublishConventionPlugin.kt
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── images
├── layout_editor.png
├── plane_inline.png
├── intro_clip_broken.png
├── intro_clip_fixed.png
├── intro_color_compat.png
├── view_path_provider.png
├── parent_matrix_defect.png
├── plane_background_broken.png
├── plane_foreground_broken.png
└── logo-icon.svg
├── .gitignore
├── settings.gradle.kts
├── gradle.properties
├── LICENSE.txt
└── gradlew.bat
/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/stubs/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/view/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/compose/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/compose/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/view/lint/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk17
--------------------------------------------------------------------------------
/core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/view/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/compose/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/stubs/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/gradle/build-logic/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.caching=true
2 | org.gradle.configuration-cache=true
--------------------------------------------------------------------------------
/images/layout_editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/images/layout_editor.png
--------------------------------------------------------------------------------
/images/plane_inline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/images/plane_inline.png
--------------------------------------------------------------------------------
/images/intro_clip_broken.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/images/intro_clip_broken.png
--------------------------------------------------------------------------------
/images/intro_clip_fixed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/images/intro_clip_fixed.png
--------------------------------------------------------------------------------
/images/intro_color_compat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/images/intro_color_compat.png
--------------------------------------------------------------------------------
/images/view_path_provider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/images/view_path_provider.png
--------------------------------------------------------------------------------
/images/parent_matrix_defect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/images/parent_matrix_defect.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/images/plane_background_broken.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/images/plane_background_broken.png
--------------------------------------------------------------------------------
/images/plane_foreground_broken.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/images/plane_foreground_broken.png
--------------------------------------------------------------------------------
/core/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | -dontwarn android.view.DisplayListCanvas
2 | -dontwarn android.view.HardwareCanvas
3 | -dontwarn android.view.RenderNode
--------------------------------------------------------------------------------
/view/lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry:
--------------------------------------------------------------------------------
1 | com.zedalpha.shadowgadgets.view.lint.ViewIssueRegistry
--------------------------------------------------------------------------------
/core/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/core/src/main/res/values/public.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zed-alpha/shadow-gadgets/HEAD/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/
3 | captures/
4 |
5 | .gradle/
6 | build/
7 |
8 | .kotlin/
9 |
10 | local.properties
11 |
12 | .DS_Store
13 |
14 | /docs
15 | /previous
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/bg_root.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/bg_rounded_rectangle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/zedalpha/shadowgadgets/core/ListUtils.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.core
2 |
3 | public inline fun List.fastForEach(block: (T) -> Unit) {
4 | for (index in indices) {
5 | val element = get(index)
6 | block(element)
7 | }
8 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Mar 04 17:55:35 EST 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/bg_drop_target.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/compose_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 15dp
4 |
5 | 12dp
6 | 6dp
7 |
8 |
--------------------------------------------------------------------------------
/view/src/main/kotlin/com/zedalpha/shadowgadgets/view/shadow/DrawPlane.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.shadow
2 |
3 | internal interface DrawPlane {
4 |
5 | fun addShadow(shadow: GroupShadow) {}
6 |
7 | fun removeShadow(shadow: GroupShadow) {}
8 |
9 | fun invalidatePlane()
10 |
11 | fun dispose() {}
12 | }
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/bg_roundish_square.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.shadowgadgets.android.library)
3 | alias(libs.plugins.shadowgadgets.publish)
4 | }
5 |
6 | android {
7 | namespace = "com.zedalpha.shadowgadgets.core"
8 | }
9 |
10 | dependencies {
11 |
12 | compileOnly(projects.stubs)
13 |
14 | implementation(libs.androidx.core.ktx)
15 | }
--------------------------------------------------------------------------------
/stubs/src/main/java/android/view/HardwareCanvas.java:
--------------------------------------------------------------------------------
1 | package android.view;
2 |
3 | import android.graphics.Canvas;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | /**
8 | * @noinspection EmptyMethod, unused
9 | */
10 | public class HardwareCanvas extends Canvas {
11 |
12 | public void drawRenderNode(@NonNull RenderNode renderNode) {}
13 | }
--------------------------------------------------------------------------------
/stubs/src/main/java/android/view/DisplayListCanvas.java:
--------------------------------------------------------------------------------
1 | package android.view;
2 |
3 | import android.graphics.Canvas;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | /**
8 | * @noinspection EmptyMethod, unused
9 | */
10 | public class DisplayListCanvas extends Canvas {
11 |
12 | public void drawRenderNode(@NonNull RenderNode renderNode) {}
13 | }
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | @Suppress("UnstableApiUsage")
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | versionCatalogs {
8 | create("libs") {
9 | from(files("../libs.versions.toml"))
10 | }
11 | }
12 | }
13 |
14 | rootProject.name = "build-logic"
15 |
16 | include(":convention")
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_clear.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_arrow_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/anim/slide_in_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
--------------------------------------------------------------------------------
/stubs/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | }
4 |
5 | android {
6 | namespace = "com.zedalpha.shadowgadgets.stubs"
7 | compileSdk = 35
8 |
9 | defaultConfig {
10 | minSdk = 21
11 | }
12 | compileOptions {
13 | sourceCompatibility = JavaVersion.VERSION_17
14 | targetCompatibility = JavaVersion.VERSION_17
15 | }
16 | }
17 |
18 | dependencies {
19 | api(libs.androidx.annotation)
20 | }
--------------------------------------------------------------------------------
/demo/src/main/res/anim/slide_in_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
--------------------------------------------------------------------------------
/demo/src/main/res/anim/slide_out_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
--------------------------------------------------------------------------------
/demo/src/main/res/anim/slide_out_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_arrow_back.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_arrow_forward.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
--------------------------------------------------------------------------------
/compose/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.shadowgadgets.android.library)
3 | alias(libs.plugins.kotlin.compose)
4 | alias(libs.plugins.shadowgadgets.publish)
5 | }
6 |
7 | android {
8 | namespace = "com.zedalpha.shadowgadgets.compose"
9 | }
10 |
11 | dependencies {
12 |
13 | implementation(projects.core)
14 |
15 | implementation(libs.androidx.core.ktx)
16 | implementation(platform(libs.compose.bom))
17 | implementation(libs.compose.ui)
18 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/zedalpha/shadowgadgets/core/shadow/BaseView.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.core.shadow
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.view.View
6 |
7 | internal abstract class BaseView(context: Context) : View(context) {
8 |
9 | override fun forceLayout() {}
10 |
11 | @SuppressLint("MissingSuperCall") override fun requestLayout() {}
12 |
13 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
14 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/zedalpha/shadowgadgets/core/layer/ManagedLayer.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.core.layer
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Paint
5 |
6 | internal interface ManagedLayer {
7 |
8 | val layerDraw: LayerDraw
9 |
10 | fun setSize(width: Int, height: Int)
11 |
12 | fun setLayerPaint(paint: Paint)
13 |
14 | fun draw(canvas: Canvas)
15 |
16 | fun recreate()
17 |
18 | fun dispose()
19 |
20 | fun invalidate() {}
21 |
22 | fun refresh() {}
23 | }
--------------------------------------------------------------------------------
/view/src/main/kotlin/com/zedalpha/shadowgadgets/view/internal/BaseDrawable.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.internal
2 |
3 | import android.graphics.ColorFilter
4 | import android.graphics.PixelFormat
5 | import android.graphics.drawable.Drawable
6 |
7 | internal abstract class BaseDrawable : Drawable() {
8 |
9 | @Suppress("OVERRIDE_DEPRECATION")
10 | override fun getOpacity() = PixelFormat.TRANSLUCENT
11 |
12 | override fun setAlpha(alpha: Int) {}
13 |
14 | override fun setColorFilter(filter: ColorFilter?) {}
15 | }
--------------------------------------------------------------------------------
/demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #44ff3333
5 | #4433ff33
6 | #443333ff
7 | #33ffffff
8 |
9 | #ffffffff
10 | #fffafafa
11 | #ffdfdfdf
12 | #ffe2e2e2
13 |
14 |
--------------------------------------------------------------------------------
/view/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.shadowgadgets.android.library)
3 | alias(libs.plugins.shadowgadgets.publish)
4 | }
5 |
6 | android {
7 | namespace = "com.zedalpha.shadowgadgets.view"
8 |
9 | buildFeatures {
10 | buildConfig = true
11 | }
12 | }
13 |
14 | dependencies {
15 |
16 | implementation(projects.core)
17 | lintPublish(projects.view.lint)
18 |
19 | implementation(libs.androidx.core.ktx)
20 | implementation(libs.androidx.appcompat)
21 | implementation(libs.material.components)
22 | }
--------------------------------------------------------------------------------
/view/lint/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | id("java-library")
5 | alias(libs.plugins.kotlin.jvm)
6 | alias(libs.plugins.android.lint)
7 | }
8 |
9 | java {
10 | sourceCompatibility = JavaVersion.VERSION_17
11 | targetCompatibility = JavaVersion.VERSION_17
12 | }
13 |
14 | kotlin {
15 | compilerOptions {
16 | jvmTarget = JvmTarget.JVM_17
17 | }
18 | }
19 |
20 | dependencies {
21 | compileOnly(libs.bundles.lint.api)
22 | testImplementation(libs.bundles.lint.tests)
23 | }
--------------------------------------------------------------------------------
/demo/src/main/res/layout/internal_title_switcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/view/src/main/kotlin/com/zedalpha/shadowgadgets/view/shadow/NullShadow.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.shadow
2 |
3 | import android.graphics.Outline
4 | import android.view.View
5 | import android.view.ViewOutlineProvider
6 |
7 | internal class NullShadow(targetView: View) : ViewShadow(targetView, null) {
8 |
9 | init {
10 | targetView.outlineProvider = object : ViewOutlineProvider() {
11 | override fun getOutline(view: View, outline: Outline) {
12 | provider.getOutline(view, outline)
13 | outline.alpha = 0F
14 | }
15 | }
16 | }
17 |
18 | override fun invalidate() {}
19 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | includeBuild("gradle/build-logic")
3 | repositories {
4 | google()
5 | mavenCentral()
6 | gradlePluginPortal()
7 | }
8 | }
9 |
10 | @Suppress("UnstableApiUsage")
11 | dependencyResolutionManagement {
12 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
13 | repositories {
14 | google()
15 | mavenCentral()
16 | }
17 | }
18 |
19 | rootProject.name = "shadow-gadgets"
20 |
21 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
22 |
23 | include(":stubs")
24 | include(":core")
25 | include(":view")
26 | include(":view:lint")
27 | include(":compose")
28 | include(":demo")
--------------------------------------------------------------------------------
/view/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | -if class androidx.constraintlayout.motion.widget.KeyFrames
2 | -keep class androidx.constraintlayout.motion.widget.KeyAttributes
3 |
4 | -if class androidx.constraintlayout.motion.widget.KeyFrames
5 | -keep class androidx.constraintlayout.motion.widget.KeyPosition
6 |
7 | -if class androidx.constraintlayout.motion.widget.KeyFrames
8 | -keep class androidx.constraintlayout.motion.widget.KeyCycle
9 |
10 | -if class androidx.constraintlayout.motion.widget.KeyFrames
11 | -keep class androidx.constraintlayout.motion.widget.KeyTimeCycle
12 |
13 | -if class androidx.constraintlayout.motion.widget.KeyFrames
14 | -keep class androidx.constraintlayout.motion.widget.KeyTrigger
--------------------------------------------------------------------------------
/view/src/main/kotlin/com/zedalpha/shadowgadgets/view/drawable/DrawableController.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.drawable
2 |
3 | import android.view.View
4 | import com.zedalpha.shadowgadgets.core.layer.Layer
5 | import com.zedalpha.shadowgadgets.view.shadow.SingleController
6 |
7 | internal class DrawableController(
8 | private val drawable: ShadowDrawable,
9 | ownerView: View?
10 | ) : SingleController(ownerView, null) {
11 |
12 | override fun shouldInvalidate(): Boolean = false
13 |
14 | override fun invalidate() = drawable.invalidateSelf()
15 |
16 | override fun onCreateLayer(layer: Layer) {
17 | super.onCreateLayer(layer)
18 | val bounds = drawable.bounds
19 | layer.setSize(bounds.width(), bounds.height())
20 | }
21 | }
--------------------------------------------------------------------------------
/demo/src/main/res/layout/dialog_root.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/compose/src/main/kotlin/com/zedalpha/shadowgadgets/compose/ExperimentalColorCompat.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.compose
2 |
3 | /**
4 | * Marks declarations that involve color compat as experimental.
5 | *
6 | * This is @OptIn only because the current implementation requires a separate
7 | * color layer for each shadow. The API is frozen, however, and this feature is
8 | * as solid as the clip, so if the overhead is acceptable for a given setup,
9 | * there shouldn't be any other issues.
10 | */
11 | @Deprecated("Opt-in requirement for color compat has been removed.")
12 | @RequiresOptIn("Color compat requires @OptIn. It is currently experimental.")
13 | @Retention(AnnotationRetention.BINARY)
14 | @Target(AnnotationTarget.FUNCTION)
15 | public annotation class ExperimentalColorCompat
--------------------------------------------------------------------------------
/view/src/main/res/values/public.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/dialog_description.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/view_root.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/zedalpha/shadowgadgets/core/shadow/CoreShadow.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.core.shadow
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Outline
5 | import androidx.annotation.CallSuper
6 | import com.zedalpha.shadowgadgets.core.Shadow
7 | import com.zedalpha.shadowgadgets.core.disableZ
8 | import com.zedalpha.shadowgadgets.core.enableZ
9 |
10 | internal abstract class CoreShadow : Shadow {
11 |
12 | protected val outline = Outline()
13 |
14 | @CallSuper
15 | override fun setOutline(outline: Outline) =
16 | this.outline.set(outline)
17 |
18 | override fun draw(canvas: Canvas) {
19 | if (!canvas.isHardwareAccelerated) return
20 |
21 | enableZ(canvas)
22 | onDraw(canvas)
23 | disableZ(canvas)
24 | }
25 |
26 | protected abstract fun onDraw(canvas: Canvas)
27 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/zedalpha/shadowgadgets/core/layer/LocationTracker.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.core.layer
2 |
3 | import android.view.View
4 |
5 | public class LocationTracker(private val view: View) {
6 |
7 | private val current = intArrayOf(Int.MAX_VALUE, Int.MAX_VALUE)
8 |
9 | public fun initialize() {
10 | view.getLocationOnScreen(current)
11 | }
12 |
13 | private val tmp = IntArray(2)
14 |
15 | public fun checkLocationChanged(): Boolean = tmp.let { location ->
16 | view.getLocationOnScreen(location)
17 | current.checkUpdate(location)
18 | }
19 |
20 | private fun IntArray.checkUpdate(other: IntArray): Boolean =
21 | if (this[0] != other[0] || this[1] != other[1]) {
22 | this[0] = other[0]; this[1] = other[1]
23 | true
24 | } else {
25 | false
26 | }
27 | }
--------------------------------------------------------------------------------
/view/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/item_description.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
23 |
24 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | `kotlin-dsl`
5 | }
6 |
7 | java {
8 | sourceCompatibility = JavaVersion.VERSION_17
9 | targetCompatibility = JavaVersion.VERSION_17
10 | }
11 |
12 | kotlin {
13 | compilerOptions {
14 | jvmTarget = JvmTarget.JVM_17
15 | }
16 | }
17 |
18 | dependencies {
19 | compileOnly(libs.android.gradle.plugin)
20 | compileOnly(libs.kotlin.gradle.plugin)
21 | compileOnly(libs.compose.gradle.plugin)
22 | }
23 |
24 | gradlePlugin {
25 | plugins {
26 | register("androidLibrary") {
27 | id = "shadowgadgets.android.library"
28 | implementationClass = "AndroidLibraryConventionPlugin"
29 | }
30 | register("publish") {
31 | id = "shadowgadgets.publish"
32 | implementationClass = "PublishConventionPlugin"
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Kotlin
2 | kotlin.code.style=official
3 | kotlin.daemon.jvmargs=-Xmx2048M
4 |
5 | # Gradle
6 | org.gradle.caching=true
7 | org.gradle.configuration-cache=true
8 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
9 | org.gradle.warning.mode=all
10 |
11 | # Android
12 | android.useAndroidX=true
13 | android.enableJetifier=false
14 | android.nonFinalResIds=true
15 | android.nonTransitiveRClass=true
16 |
17 | android.defaults.buildfeatures.aidl=false
18 | android.defaults.buildfeatures.renderscript=false
19 | android.defaults.buildfeatures.resvalues=false
20 | android.defaults.buildfeatures.shaders=false
21 |
22 | android.lint.useK2Uast=true
23 |
24 | # Dokka
25 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
26 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
27 |
28 | # Developer
29 | developer.name=zed-alpha
30 | repository.url=https://github.com/zed-alpha/shadow-gadgets
31 | group.id=com.zedalpha.shadowgadgets
32 | library.version=2.3.2
--------------------------------------------------------------------------------
/demo/src/main/res/layout/item_colorful.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
22 |
23 |
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/RelativeOverlapDetectorSG.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.tools.lint.checks.RelativeOverlapDetector
4 | import com.android.tools.lint.detector.api.Implementation
5 | import com.android.tools.lint.detector.api.Scope
6 | import com.zedalpha.shadowgadgets.view.lint.internal.BaseDetector
7 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_RELATIVE_LAYOUT
8 |
9 | class RelativeOverlapDetectorSG : BaseDetector() {
10 |
11 | companion object {
12 |
13 | @JvmField
14 | val ISSUE_SG = RelativeOverlapDetector.ISSUE.copy(
15 | Implementation(
16 | RelativeOverlapDetectorSG::class.java,
17 | Scope.RESOURCE_FILE_SCOPE
18 | )
19 | )
20 | }
21 |
22 | override val detector = RelativeOverlapDetector()
23 |
24 | override val issues = mapOf(
25 | RelativeOverlapDetector.ISSUE to ISSUE_SG
26 | )
27 |
28 | override val elements = listOf(SHADOWS_RELATIVE_LAYOUT)
29 | }
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/MotionLayoutIdDetectorSG.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.tools.lint.checks.MotionLayoutIdDetector
4 | import com.android.tools.lint.detector.api.Implementation
5 | import com.android.tools.lint.detector.api.Scope
6 | import com.zedalpha.shadowgadgets.view.lint.internal.BaseDetector
7 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_MOTION_LAYOUT
8 |
9 | class MotionLayoutIdDetectorSG : BaseDetector() {
10 |
11 | companion object {
12 |
13 | @JvmField
14 | val MISSING_ID_SG = MotionLayoutIdDetector.MISSING_ID.copy(
15 | Implementation(
16 | MotionLayoutIdDetectorSG::class.java,
17 | Scope.RESOURCE_FILE_SCOPE
18 | )
19 | )
20 | }
21 |
22 | override val detector = MotionLayoutIdDetector()
23 |
24 | override val issues = mapOf(
25 | MotionLayoutIdDetector.MISSING_ID to MISSING_ID_SG
26 | )
27 |
28 | override val elements = listOf(SHADOWS_MOTION_LAYOUT)
29 | }
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/UseCompoundDrawableDetectorSG.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.tools.lint.checks.UseCompoundDrawableDetector
4 | import com.android.tools.lint.detector.api.Implementation
5 | import com.android.tools.lint.detector.api.Scope
6 | import com.zedalpha.shadowgadgets.view.lint.internal.BaseDetector
7 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_LINEAR_LAYOUT
8 |
9 | class UseCompoundDrawableDetectorSG : BaseDetector() {
10 |
11 | companion object {
12 |
13 | @JvmField
14 | val ISSUE_SG = UseCompoundDrawableDetector.ISSUE.copy(
15 | Implementation(
16 | UseCompoundDrawableDetectorSG::class.java,
17 | Scope.RESOURCE_FILE_SCOPE
18 | )
19 | )
20 | }
21 |
22 | override val detector = UseCompoundDrawableDetector()
23 |
24 | override val issues = mapOf(
25 | UseCompoundDrawableDetector.ISSUE to ISSUE_SG
26 | )
27 |
28 | override val elements = listOf(SHADOWS_LINEAR_LAYOUT)
29 | }
--------------------------------------------------------------------------------
/demo/src/main/kotlin/com/zedalpha/shadowgadgets/demo/internal/ContentCardView.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.demo.internal
2 |
3 | import android.content.Context
4 | import android.content.res.ColorStateList
5 | import android.graphics.Canvas
6 | import android.graphics.Color
7 | import android.util.AttributeSet
8 | import com.google.android.material.card.MaterialCardView
9 | import kotlin.math.roundToInt
10 |
11 | class ContentCardView @JvmOverloads constructor(
12 | context: Context,
13 | attrs: AttributeSet? = null
14 | ) : MaterialCardView(context, attrs) {
15 |
16 | private val drawable = SlantGridDrawable()
17 |
18 | init {
19 | setCardBackgroundColor(Color.WHITE)
20 | setStrokeColor(ColorStateList.valueOf(Color.LTGRAY))
21 | strokeWidth = resources.displayMetrics.density.roundToInt()
22 | }
23 |
24 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
25 | drawable.setBounds(0, 0, w, h)
26 | }
27 |
28 | override fun onDraw(canvas: Canvas) {
29 | drawable.draw(canvas)
30 | super.onDraw(canvas)
31 | }
32 | }
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 zed-alpha
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.
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/ChildCountDetectorSG.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.tools.lint.checks.ChildCountDetector
4 | import com.android.tools.lint.detector.api.Implementation
5 | import com.android.tools.lint.detector.api.Scope
6 | import com.zedalpha.shadowgadgets.view.lint.internal.BaseDetector
7 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_GRID_VIEW
8 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_LIST_VIEW
9 |
10 | class ChildCountDetectorSG : BaseDetector() {
11 |
12 | companion object {
13 |
14 | @JvmField
15 | val ADAPTER_VIEW_ISSUE_SG = ChildCountDetector.ADAPTER_VIEW_ISSUE.copy(
16 | Implementation(
17 | ChildCountDetectorSG::class.java,
18 | Scope.RESOURCE_FILE_SCOPE
19 | )
20 | )
21 | }
22 |
23 | override val detector = ChildCountDetector()
24 |
25 | override val issues = mapOf(
26 | ChildCountDetector.ADAPTER_VIEW_ISSUE to ADAPTER_VIEW_ISSUE_SG
27 | )
28 |
29 | override val elements = listOf(SHADOWS_GRID_VIEW, SHADOWS_LIST_VIEW)
30 | }
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/internal/ElementWrapper.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint.internal
2 |
3 | import com.android.AndroidXConstants
4 | import com.android.SdkConstants
5 | import org.w3c.dom.Element
6 | import org.w3c.dom.Node
7 |
8 | internal class ElementWrapper(
9 | private val wrapped: Element,
10 | ) : Element by wrapped {
11 |
12 | override fun getParentNode(): Node = run {
13 | val node = wrapped.parentNode
14 | if (node is Element) ElementWrapper(node) else node
15 | }
16 |
17 | override fun getTagName(): String =
18 | when (val actual = wrapped.tagName) {
19 | // NestedScrollingWidgetDetector
20 | SHADOWS_LIST_VIEW -> SdkConstants.LIST_VIEW
21 | SHADOWS_GRID_VIEW -> SdkConstants.GRID_VIEW
22 | // WrongIdDetector
23 | SHADOWS_RELATIVE_LAYOUT -> SdkConstants.RELATIVE_LAYOUT
24 | SHADOWS_CONSTRAINT_LAYOUT -> AndroidXConstants.CONSTRAINT_LAYOUT.newName()
25 | else -> actual
26 | }
27 |
28 | override fun toString(): String {
29 | return wrapped.toString()
30 | }
31 | }
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/ConstraintLayoutDetectorSG.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.tools.lint.checks.ConstraintLayoutDetector
4 | import com.android.tools.lint.detector.api.Implementation
5 | import com.android.tools.lint.detector.api.Scope
6 | import com.zedalpha.shadowgadgets.view.lint.internal.BaseDetector
7 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_CONSTRAINT_LAYOUT
8 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_MOTION_LAYOUT
9 |
10 | class ConstraintLayoutDetectorSG : BaseDetector() {
11 |
12 | companion object {
13 |
14 | @JvmField
15 | val ISSUE_SG = ConstraintLayoutDetector.ISSUE.copy(
16 | Implementation(
17 | ConstraintLayoutDetectorSG::class.java,
18 | Scope.RESOURCE_FILE_SCOPE
19 | )
20 | )
21 | }
22 |
23 | override val detector = ConstraintLayoutDetector()
24 |
25 | override val issues = mapOf(
26 | ConstraintLayoutDetector.ISSUE to ISSUE_SG
27 | )
28 |
29 | override val elements = listOf(
30 | SHADOWS_CONSTRAINT_LAYOUT,
31 | SHADOWS_MOTION_LAYOUT
32 | )
33 | }
--------------------------------------------------------------------------------
/demo/src/main/res/layout/fragment_root.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
29 |
30 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.LibraryExtension
2 | import org.gradle.api.JavaVersion
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.assign
6 | import org.gradle.kotlin.dsl.configure
7 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
8 | import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
9 |
10 | class AndroidLibraryConventionPlugin : Plugin {
11 |
12 | override fun apply(target: Project) = with(target) {
13 | with(pluginManager) {
14 | apply("com.android.library")
15 | apply("org.jetbrains.kotlin.android")
16 | }
17 |
18 | extensions.configure {
19 | compileSdk = 35
20 | defaultConfig.minSdk = 21
21 | buildTypes.all { consumerProguardFiles("consumer-rules.pro") }
22 | compileOptions {
23 | sourceCompatibility = JavaVersion.VERSION_17
24 | targetCompatibility = JavaVersion.VERSION_17
25 | }
26 | }
27 |
28 | configure {
29 | compilerOptions.jvmTarget = JvmTarget.JVM_17
30 | explicitApi()
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/PublishConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.LibraryExtension
2 | import org.gradle.api.Plugin
3 | import org.gradle.api.Project
4 | import org.gradle.api.publish.PublishingExtension
5 | import org.gradle.api.publish.maven.MavenPublication
6 | import org.gradle.kotlin.dsl.apply
7 | import org.gradle.kotlin.dsl.create
8 | import org.gradle.kotlin.dsl.get
9 | import org.gradle.kotlin.dsl.the
10 |
11 | class PublishConventionPlugin : Plugin {
12 |
13 | override fun apply(target: Project) = with(target) {
14 | val libraryGroupId = findProperty("group.id")!!.toString()
15 | val libraryVersion = findProperty("library.version")!!.toString()
16 |
17 | apply(plugin = "com.android.library")
18 | the().publishing {
19 | singleVariant("release") { withSourcesJar() }
20 | }
21 |
22 | apply(plugin = "maven-publish")
23 | afterEvaluate {
24 | the().publications {
25 | create("release") {
26 | from(components["release"])
27 | groupId = libraryGroupId
28 | artifactId = this@with.name
29 | version = libraryVersion
30 | }
31 | }
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/demo/src/main/kotlin/com/zedalpha/shadowgadgets/demo/topic/PlaneTopic.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.demo.topic
2 |
3 | import com.zedalpha.shadowgadgets.demo.R
4 | import com.zedalpha.shadowgadgets.demo.databinding.FragmentPlaneBinding
5 | import com.zedalpha.shadowgadgets.view.ShadowPlane
6 | import com.zedalpha.shadowgadgets.view.clipOutlineShadow
7 | import com.zedalpha.shadowgadgets.view.shadowPlane
8 |
9 | internal val PlaneTopic = Topic(
10 | "Plane",
11 | R.string.description_plane,
12 | PlaneFragment::class.java
13 | )
14 |
15 | class PlaneFragment : TopicFragment(
16 | FragmentPlaneBinding::inflate
17 | ) {
18 | override fun loadUi(ui: FragmentPlaneBinding) {
19 | ui.clipSwitch.setOnCheckedChangeListener { _, isChecked ->
20 | ui.motionView.clipOutlineShadow = isChecked
21 | ui.centerView.clipOutlineShadow = isChecked
22 | }
23 | ui.planeSelect.setOnCheckedChangeListener { _, checkedId ->
24 | val plane = when (checkedId) {
25 | R.id.foreground_selection -> ShadowPlane.Foreground
26 | R.id.background_selection -> ShadowPlane.Background
27 | else -> ShadowPlane.Inline
28 | }
29 | ui.motionView.shadowPlane = plane
30 | ui.centerView.shadowPlane = plane
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/view/src/main/kotlin/com/zedalpha/shadowgadgets/view/shadow/SingleController.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.shadow
2 |
3 | import android.view.View
4 | import androidx.annotation.CallSuper
5 | import com.zedalpha.shadowgadgets.core.isNotDefault
6 | import com.zedalpha.shadowgadgets.core.layer.Layer
7 | import com.zedalpha.shadowgadgets.core.rendernode.RenderNodeFactory
8 |
9 | internal abstract class SingleController(
10 | ownerView: View?,
11 | layerSizeView: View?
12 | ) : ShadowController(ownerView, layerSizeView) {
13 |
14 | protected var coreLayer: Layer? = null
15 |
16 | @CallSuper
17 | override fun checkInvalidate() {
18 | if (shouldInvalidate()) {
19 | invalidate()
20 | } else if (!RenderNodeFactory.isOpen) {
21 | coreLayer?.refresh()
22 | }
23 | }
24 |
25 | protected abstract fun shouldInvalidate(): Boolean
26 |
27 | @CallSuper
28 | override fun onCreateLayer(layer: Layer) {
29 | coreLayer = layer
30 | }
31 |
32 | @CallSuper
33 | override fun onDisposeLayer(layer: Layer) {
34 | coreLayer = null
35 | }
36 |
37 | @CallSuper
38 | override fun hasColorLayer(): Boolean =
39 | coreLayer?.color?.isNotDefault == true
40 |
41 | @CallSuper
42 | override fun recreateColorLayers() {
43 | coreLayer?.recreate()
44 | }
45 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/zedalpha/shadowgadgets/core/layer/RenderNodeLayer.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.core.layer
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Paint
5 | import com.zedalpha.shadowgadgets.core.rendernode.RenderNodeFactory
6 |
7 | internal class RenderNodeLayer(override val layerDraw: LayerDraw) :
8 | ManagedLayer {
9 |
10 | private var layerNode = RenderNodeFactory.newInstance("ManagedLayer")
11 |
12 | override fun recreate() {
13 | layerNode.discardDisplayList()
14 | layerNode = RenderNodeFactory.newInstance("ManagedLayer")
15 | }
16 |
17 | override fun dispose() = layerNode.discardDisplayList()
18 |
19 | private var width = 0
20 |
21 | private var height = 0
22 |
23 | override fun setSize(width: Int, height: Int) {
24 | this.width = width
25 | this.height = height
26 | layerNode.setPosition(0, 0, width, height)
27 | }
28 |
29 | override fun setLayerPaint(paint: Paint) {
30 | layerNode.setUseCompositingLayer(true, paint)
31 | }
32 |
33 | override fun draw(canvas: Canvas) {
34 | val node = layerNode
35 | val nodeCanvas = node.beginRecording(width, height)
36 | try {
37 | layerDraw.draw(nodeCanvas)
38 | } finally {
39 | node.endRecording(nodeCanvas)
40 | }
41 | node.draw(canvas)
42 | }
43 | }
--------------------------------------------------------------------------------
/demo/src/main/kotlin/com/zedalpha/shadowgadgets/demo/topic/Topic.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.demo.topic
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.view.ViewGroup.LayoutParams.MATCH_PARENT
8 | import androidx.fragment.app.Fragment
9 | import androidx.viewbinding.ViewBinding
10 | import com.zedalpha.shadowgadgets.demo.internal.ContentCardView
11 |
12 | internal class Topic>(
13 | val title: String,
14 | val descriptionResId: Int,
15 | private val fragmentClass: Class
16 | ) {
17 | fun createFragment(): T = fragmentClass.getConstructor().newInstance()
18 | }
19 |
20 | abstract class TopicFragment(
21 | private val inflate: (LayoutInflater, ViewGroup?, Boolean) -> T
22 | ) : Fragment() {
23 |
24 | protected lateinit var ui: T
25 |
26 | final override fun onCreateView(
27 | inflater: LayoutInflater,
28 | container: ViewGroup?,
29 | savedInstanceState: Bundle?
30 | ) = ContentCardView(requireContext()).apply {
31 | layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
32 | ui = inflate(inflater, this, true)
33 | }
34 |
35 | final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
36 | loadUi(ui)
37 | }
38 |
39 | abstract fun loadUi(ui: T)
40 | }
--------------------------------------------------------------------------------
/view/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/MotionLayoutDetectorSG.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.SdkConstants.MotionSceneTags.MOTION_SCENE
4 | import com.android.tools.lint.checks.MotionLayoutDetector
5 | import com.android.tools.lint.detector.api.Context
6 | import com.android.tools.lint.detector.api.Implementation
7 | import com.android.tools.lint.detector.api.Scope
8 | import com.zedalpha.shadowgadgets.view.lint.internal.BaseDetector
9 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_MOTION_LAYOUT
10 |
11 | class MotionLayoutDetectorSG : BaseDetector() {
12 |
13 | companion object {
14 |
15 | @JvmField
16 | val INVALID_SCENE_FILE_REFERENCE_SG =
17 | MotionLayoutDetector.INVALID_SCENE_FILE_REFERENCE.copy(
18 | Implementation(
19 | MotionLayoutDetectorSG::class.java,
20 | Scope.RESOURCE_FILE_SCOPE
21 | )
22 | )
23 | }
24 |
25 | override val detector = MotionLayoutDetector()
26 |
27 | override val issues = mapOf(
28 | MotionLayoutDetector.INVALID_SCENE_FILE_REFERENCE to
29 | INVALID_SCENE_FILE_REFERENCE_SG
30 | )
31 |
32 | override val elements = listOf(SHADOWS_MOTION_LAYOUT, MOTION_SCENE)
33 |
34 | override fun afterCheckRootProject(context: Context) {
35 | detector.afterCheckRootProject(contextWrapper)
36 | }
37 | }
--------------------------------------------------------------------------------
/demo/src/main/kotlin/com/zedalpha/shadowgadgets/demo/internal/TitleSwitcher.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.demo.internal
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.LayoutInflater
6 | import android.view.animation.AnimationUtils
7 | import android.widget.TextSwitcher
8 | import com.zedalpha.shadowgadgets.demo.R
9 |
10 | class TitleSwitcher @JvmOverloads constructor(
11 | context: Context,
12 | attributeSet: AttributeSet? = null
13 | ) : TextSwitcher(context, attributeSet) {
14 |
15 | private val forward = Pair(
16 | AnimationUtils.loadAnimation(context, R.anim.slide_in_top),
17 | AnimationUtils.loadAnimation(context, R.anim.slide_out_bottom)
18 | )
19 |
20 | private val backward = Pair(
21 | AnimationUtils.loadAnimation(context, R.anim.slide_in_bottom),
22 | AnimationUtils.loadAnimation(context, R.anim.slide_out_top)
23 | )
24 |
25 | init {
26 | val inflater = LayoutInflater.from(context)
27 | setFactory {
28 | inflater.inflate(
29 | R.layout.internal_title_switcher,
30 | this,
31 | false
32 | )
33 | }
34 | setDirection(true)
35 | }
36 |
37 | fun setDirection(goForward: Boolean) {
38 | if (goForward) {
39 | inAnimation = forward.first; outAnimation = forward.second
40 | } else {
41 | inAnimation = backward.first; outAnimation = backward.second
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/ObjectAnimatorDetectorSG.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.tools.lint.checks.ObjectAnimatorDetector
4 | import com.android.tools.lint.detector.api.Context
5 | import com.android.tools.lint.detector.api.Implementation
6 | import com.android.tools.lint.detector.api.Incident
7 | import com.android.tools.lint.detector.api.LintMap
8 | import com.android.tools.lint.detector.api.Scope
9 | import com.zedalpha.shadowgadgets.view.lint.internal.BaseDetector
10 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_MOTION_LAYOUT
11 |
12 | class ObjectAnimatorDetectorSG : BaseDetector() {
13 |
14 | companion object {
15 |
16 | @JvmField
17 | val MISSING_KEEP_SG = ObjectAnimatorDetector.MISSING_KEEP.copy(
18 | Implementation(
19 | ObjectAnimatorDetectorSG::class.java,
20 | Scope.RESOURCE_FILE_SCOPE
21 | )
22 | )
23 | }
24 |
25 | override val detector = ObjectAnimatorDetector()
26 |
27 | override val issues = mapOf(
28 | ObjectAnimatorDetector.MISSING_KEEP to MISSING_KEEP_SG
29 | )
30 |
31 | override val elements = listOf(SHADOWS_MOTION_LAYOUT, "CustomAttribute")
32 |
33 | override fun filterIncident(
34 | context: Context,
35 | incident: Incident,
36 | map: LintMap
37 | ): Boolean = detector.filterIncident(context, incident, map)
38 |
39 | override fun getApplicableMethodNames(): List =
40 | detector.getApplicableMethodNames()
41 | }
--------------------------------------------------------------------------------
/demo/src/main/res/layout/view_panel.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
23 |
24 |
34 |
35 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/fragment_compat_stress_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
24 |
25 |
34 |
35 |
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/zedalpha/shadowgadgets/core/rendernode/RenderNodeApi23.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.core.rendernode
2 |
3 | import android.graphics.Canvas
4 | import android.os.Build
5 | import android.view.DisplayListCanvas
6 | import android.view.RenderNode
7 | import androidx.annotation.DoNotInline
8 | import androidx.annotation.RequiresApi
9 |
10 | @RequiresApi(23)
11 | internal class RenderNodeApi23(name: String?) : RenderNodeApi21(name) {
12 |
13 | override fun draw(canvas: Canvas) {
14 | if (!renderNode.isValid) recordEmptyDisplayList()
15 | (canvas as DisplayListCanvas).drawRenderNode(renderNode)
16 | }
17 |
18 | private fun recordEmptyDisplayList() {
19 | val canvas = renderNode.start(0, 0)
20 | renderNode.end(canvas)
21 | }
22 |
23 | override fun beginRecording(width: Int, height: Int): Canvas =
24 | renderNode.start(width, height)
25 |
26 | override fun endRecording(canvas: Canvas) =
27 | renderNode.end(canvas as DisplayListCanvas)
28 |
29 | override fun discardDisplayList() =
30 | if (Build.VERSION.SDK_INT >= 24) {
31 | RenderNodeDiscardHelper24.discardDisplayList(renderNode)
32 | } else {
33 | RenderNodeDiscardHelper21.destroyDisplayListData(renderNode)
34 | }
35 | }
36 |
37 | private object RenderNodeDiscardHelper21 {
38 |
39 | @DoNotInline
40 | fun destroyDisplayListData(renderNode: RenderNode) =
41 | renderNode.destroyDisplayListData()
42 | }
43 |
44 | @RequiresApi(24)
45 | private object RenderNodeDiscardHelper24 {
46 |
47 | @DoNotInline
48 | fun discardDisplayList(renderNode: RenderNode) =
49 | renderNode.discardDisplayList()
50 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/zedalpha/shadowgadgets/core/layer/ViewLayer.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.core.layer
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Paint
5 | import android.view.View
6 | import android.view.View.LAYER_TYPE_HARDWARE
7 | import androidx.core.view.isVisible
8 | import com.zedalpha.shadowgadgets.core.shadow.BaseView
9 | import com.zedalpha.shadowgadgets.core.shadow.ViewPainterProxy
10 |
11 | internal class ViewLayer(
12 | private val ownerView: View,
13 | override val layerDraw: LayerDraw,
14 | ) : ManagedLayer {
15 |
16 | private fun createLayerView() = object : BaseView(ownerView.context) {
17 |
18 | init {
19 | isVisible = false
20 | layout(0, 0, ownerView.width, ownerView.height)
21 | }
22 |
23 | override fun onDraw(canvas: Canvas) = layerDraw.draw(canvas)
24 | }
25 |
26 | private var layerView = createLayerView()
27 |
28 | private val painter = ViewPainterProxy(ownerView, layerView)
29 |
30 | override fun recreate() {
31 | val newLayerView = createLayerView()
32 | painter.replaceLayerView(layerView, newLayerView)
33 | layerView = newLayerView
34 | }
35 |
36 | override fun dispose() = painter.dispose()
37 |
38 | override fun setSize(width: Int, height: Int) =
39 | layerView.layout(0, 0, width, height)
40 |
41 | override fun setLayerPaint(paint: Paint) =
42 | layerView.setLayerType(LAYER_TYPE_HARDWARE, paint)
43 |
44 | override fun draw(canvas: Canvas) =
45 | painter.drawLayerView(canvas, layerView)
46 |
47 | override fun invalidate() = painter.invalidateLayerView(layerView)
48 |
49 | override fun refresh() = painter.refreshLayerView(layerView)
50 | }
--------------------------------------------------------------------------------
/demo/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
18 |
19 |
22 |
23 |
33 |
34 |
40 |
41 |
--------------------------------------------------------------------------------
/images/logo-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/view/src/main/kotlin/com/zedalpha/shadowgadgets/view/shadow/SoloController.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.shadow
2 |
3 | import android.content.Context
4 | import android.content.Context.WINDOW_SERVICE
5 | import android.graphics.Point
6 | import android.os.Build
7 | import android.util.Size
8 | import android.view.View
9 | import android.view.WindowManager
10 | import com.zedalpha.shadowgadgets.core.layer.Layer
11 |
12 | internal class SoloController(
13 | ownerView: View,
14 | shadowScope: View?
15 | ) : SingleController(ownerView, ownerView.scopeView) {
16 |
17 | val shadow by lazy { SoloShadow(ownerView, this, shadowScope) }
18 |
19 | override fun shouldInvalidate(): Boolean = shadow.shouldInvalidate()
20 |
21 | override fun invalidate() = shadow.invalidate()
22 |
23 | var layerSize: Size = getScreenSize()
24 | private set
25 |
26 | override fun onCreateLayer(layer: Layer) {
27 | super.onCreateLayer(layer)
28 | val scope = scopeView
29 | val size = if (scope != null) {
30 | Size(scope.width, scope.height)
31 | } else {
32 | getScreenSize()
33 | }
34 | layerSize = size
35 | layer.setSize(size.width, size.height)
36 | }
37 |
38 | private fun getScreenSize() = ownerView!!.context.getScreenSize()
39 |
40 | override fun onLayerSizeChanged(width: Int, height: Int) {
41 | if (scopeView != null) coreLayer?.setSize(width, height)
42 | }
43 | }
44 |
45 | internal fun Context.getScreenSize(): Size {
46 | val manager = getSystemService(WINDOW_SERVICE) as WindowManager
47 | return if (Build.VERSION.SDK_INT >= 30) {
48 | val bounds = manager.currentWindowMetrics.bounds
49 | Size(bounds.width(), bounds.height())
50 | } else {
51 | val point = Point()
52 | @Suppress("DEPRECATION")
53 | manager.defaultDisplay.getSize(point)
54 | Size(point.x, point.y)
55 | }
56 | }
--------------------------------------------------------------------------------
/demo/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | }
6 |
7 | android {
8 | namespace = "com.zedalpha.shadowgadgets.demo"
9 | compileSdk = 35
10 |
11 | defaultConfig {
12 | applicationId = "com.zedalpha.shadowgadgets.demo"
13 | minSdk = 21
14 | targetSdk = 35
15 | versionCode = 1
16 | versionName = "1.0"
17 | vectorDrawables {
18 | useSupportLibrary = true
19 | }
20 | }
21 | buildFeatures {
22 | viewBinding = true
23 | compose = true
24 | }
25 | buildTypes {
26 | release {
27 | isMinifyEnabled = true
28 | isShrinkResources = true
29 | proguardFiles(
30 | getDefaultProguardFile("proguard-android-optimize.txt")
31 | )
32 | signingConfig = signingConfigs.getByName("debug")
33 | }
34 | }
35 | compileOptions {
36 | sourceCompatibility = JavaVersion.VERSION_17
37 | targetCompatibility = JavaVersion.VERSION_17
38 | }
39 | kotlinOptions {
40 | jvmTarget = JavaVersion.VERSION_17.toString()
41 | }
42 | packaging {
43 | resources {
44 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
45 | }
46 | }
47 | }
48 |
49 | dependencies {
50 |
51 | implementation(projects.view)
52 | implementation(projects.compose)
53 |
54 | implementation(libs.androidx.core.ktx)
55 | implementation(libs.material.components)
56 | implementation(libs.androidx.constraintlayout)
57 | implementation(platform(libs.compose.bom))
58 | implementation(libs.compose.foundation)
59 | implementation(libs.compose.material)
60 | implementation(libs.compose.runtime)
61 | implementation(libs.compose.ui)
62 | debugImplementation(libs.compose.ui.tooling)
63 | debugImplementation(libs.compose.ui.tooling.preview)
64 | }
--------------------------------------------------------------------------------
/demo/src/main/res/layout/dialog_welcome.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
20 |
21 |
31 |
32 |
38 |
39 |
40 |
41 |
50 |
51 |
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/ViewIssueRegistry.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.tools.lint.client.api.IssueRegistry
4 | import com.android.tools.lint.client.api.Vendor
5 | import com.android.tools.lint.detector.api.CURRENT_API
6 | import com.android.tools.lint.detector.api.Issue
7 |
8 | class ViewIssueRegistry : IssueRegistry() {
9 |
10 | override val api: Int get() = CURRENT_API
11 |
12 | override val minApi: Int get() = 12
13 |
14 | override val issues: List = allIssues
15 |
16 | override val vendor: Vendor = Vendor(
17 | vendorName = "zed-alpha",
18 | feedbackUrl = "https://github.com/zed-alpha/shadow-gadgets/issues"
19 | )
20 |
21 | companion object {
22 |
23 | private val allIssues = listOf(
24 | ChildCountDetectorSG.ADAPTER_VIEW_ISSUE_SG,
25 | ConstraintLayoutDetectorSG.ISSUE_SG,
26 | InefficientWeightDetectorSG.BASELINE_WEIGHTS_SG,
27 | InefficientWeightDetectorSG.INEFFICIENT_WEIGHT_SG,
28 | InefficientWeightDetectorSG.NESTED_WEIGHTS_SG,
29 | InefficientWeightDetectorSG.ORIENTATION_SG,
30 | InefficientWeightDetectorSG.WRONG_0DP_SG,
31 | MotionLayoutDetectorSG.INVALID_SCENE_FILE_REFERENCE_SG,
32 | MotionLayoutIdDetectorSG.MISSING_ID_SG,
33 | NestedScrollingWidgetDetectorSG.ISSUE_SG,
34 | ObjectAnimatorDetectorSG.MISSING_KEEP_SG,
35 | RelativeOverlapDetectorSG.ISSUE_SG,
36 | UseCompoundDrawableDetectorSG.ISSUE_SG,
37 | UselessViewDetectorSG.USELESS_LEAF_SG,
38 | UselessViewDetectorSG.USELESS_PARENT_SG,
39 | WrongIdDetectorSG.UNKNOWN_ID_SG,
40 | WrongIdDetectorSG.NOT_SIBLING_SG,
41 | WrongIdDetectorSG.INVALID_SG,
42 | WrongIdDetectorSG.UNKNOWN_ID_LAYOUT_SG,
43 | ShadowAttributesDetector.MISSING_ID
44 | )
45 | }
46 | }
--------------------------------------------------------------------------------
/demo/src/main/kotlin/com/zedalpha/shadowgadgets/demo/internal/SlantGridDrawable.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.demo.internal
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Color
5 | import android.graphics.LinearGradient
6 | import android.graphics.Rect
7 | import android.graphics.Shader
8 | import android.graphics.drawable.PaintDrawable
9 | import androidx.annotation.ColorInt
10 | import androidx.core.graphics.withClip
11 |
12 | internal class SlantGridDrawable(
13 | @ColorInt private val startColor: Int = 0xffc0c0c0.toInt(),
14 | @ColorInt private val endColor: Int = 0xffcecece.toInt(),
15 | private val columns: Int = 19
16 | ) : PaintDrawable() {
17 |
18 | override fun onBoundsChange(bounds: Rect) {
19 | paint.shader = LinearGradient(
20 | 0F,
21 | 0F,
22 | bounds.width().toFloat(),
23 | bounds.height().toFloat(),
24 | startColor,
25 | endColor,
26 | Shader.TileMode.CLAMP
27 | )
28 | }
29 |
30 | override fun draw(canvas: Canvas) {
31 | val bounds = bounds
32 | val paint = paint
33 | val columns = columns
34 |
35 | val width = bounds.width()
36 | val height = bounds.height()
37 | // https://math.stackexchange.com/q/49020
38 | val fitWidth = width * 1.1F
39 | val fitHeight = height * 1.1F
40 |
41 | canvas.withClip(bounds) {
42 | drawColor(Color.WHITE)
43 | translate(-width * .055F, -height * .055F)
44 | rotate(-4F, fitWidth / 2, fitHeight / 2)
45 | val dim = fitWidth / columns
46 | for (col in 0..columns) {
47 | val x = (dim * col)
48 | drawLine(x, 0F, x, fitHeight, paint)
49 | }
50 | var y = 0F
51 | while (y < fitHeight) {
52 | y += dim
53 | drawLine(0F, y, fitWidth, y, paint)
54 | }
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/view/src/main/kotlin/com/zedalpha/shadowgadgets/view/viewgroup/RecyclingManager.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.viewgroup
2 |
3 | import android.graphics.Canvas
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import com.zedalpha.shadowgadgets.view.clipOutlineShadow
8 | import com.zedalpha.shadowgadgets.view.forceOutlineShadowColorCompat
9 | import com.zedalpha.shadowgadgets.view.outlineShadowColorCompat
10 | import com.zedalpha.shadowgadgets.view.shadow.createShadow
11 | import com.zedalpha.shadowgadgets.view.shadow.isInitializedForRecyclingSVG
12 | import com.zedalpha.shadowgadgets.view.shadowPlane
13 |
14 | internal class RecyclingManager(
15 | parentView: ViewGroup,
16 | attributeSet: AttributeSet?,
17 | attachViewToParent: (View, Int, ViewGroup.LayoutParams) -> Unit,
18 | detachAllViewsFromParent: () -> Unit,
19 | superDispatchDraw: (Canvas) -> Unit,
20 | superDrawChild: (Canvas, View, Long) -> Boolean
21 | ) : ShadowsViewGroupManager(
22 | parentView,
23 | attributeSet,
24 | attachViewToParent,
25 | detachAllViewsFromParent,
26 | superDispatchDraw,
27 | superDrawChild
28 | ) {
29 | override fun onViewAdded(child: View) {
30 | if (child.isInitializedForRecyclingSVG) return
31 |
32 | if (child.planeNotSet && groupPlaneSet) {
33 | child.shadowPlane = childShadowsPlane
34 | }
35 |
36 | // Recycling SVGs clip all of their children's shadows by default.
37 | if (child.clipNotSet) child.clipOutlineShadow =
38 | if (groupClipSet) clipAllChildShadows else true
39 |
40 | if (child.colorNotSet && groupColorSet) {
41 | child.outlineShadowColorCompat =
42 | childOutlineShadowsColorCompat
43 | }
44 |
45 | if (child.forceNotSet && groupForceSet) {
46 | child.forceOutlineShadowColorCompat =
47 | forceChildOutlineShadowsColorCompat
48 | }
49 |
50 | child.isInitializedForRecyclingSVG = true
51 |
52 | child.createShadow()
53 | }
54 | }
--------------------------------------------------------------------------------
/view/lint/src/main/kotlin/com/zedalpha/shadowgadgets/view/lint/InefficientWeightDetectorSG.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.SdkConstants
4 | import com.android.tools.lint.checks.InefficientWeightDetector
5 | import com.android.tools.lint.detector.api.Implementation
6 | import com.android.tools.lint.detector.api.Scope
7 | import com.zedalpha.shadowgadgets.view.lint.internal.BaseDetector
8 | import com.zedalpha.shadowgadgets.view.lint.internal.SHADOWS_LINEAR_LAYOUT
9 |
10 | class InefficientWeightDetectorSG : BaseDetector() {
11 |
12 | companion object {
13 |
14 | private val implementation = Implementation(
15 | InefficientWeightDetectorSG::class.java,
16 | Scope.RESOURCE_FILE_SCOPE
17 | )
18 |
19 | @JvmField
20 | val BASELINE_WEIGHTS_SG =
21 | InefficientWeightDetector.BASELINE_WEIGHTS.copy(implementation)
22 |
23 | @JvmField
24 | val INEFFICIENT_WEIGHT_SG =
25 | InefficientWeightDetector.INEFFICIENT_WEIGHT.copy(implementation)
26 |
27 | @JvmField
28 | val NESTED_WEIGHTS_SG =
29 | InefficientWeightDetector.NESTED_WEIGHTS.copy(implementation)
30 |
31 | @JvmField
32 | val ORIENTATION_SG =
33 | InefficientWeightDetector.ORIENTATION.copy(implementation)
34 |
35 | @JvmField
36 | val WRONG_0DP_SG =
37 | InefficientWeightDetector.WRONG_0DP.copy(implementation)
38 | }
39 |
40 | override val detector = InefficientWeightDetector()
41 |
42 | override val issues = mapOf(
43 | InefficientWeightDetector.BASELINE_WEIGHTS to BASELINE_WEIGHTS_SG,
44 | InefficientWeightDetector.INEFFICIENT_WEIGHT to INEFFICIENT_WEIGHT_SG,
45 | InefficientWeightDetector.NESTED_WEIGHTS to NESTED_WEIGHTS_SG,
46 | InefficientWeightDetector.ORIENTATION to ORIENTATION_SG,
47 | InefficientWeightDetector.WRONG_0DP to WRONG_0DP_SG
48 | )
49 |
50 | override val elements = listOf(
51 | SHADOWS_LINEAR_LAYOUT,
52 | SdkConstants.LINEAR_LAYOUT
53 | )
54 | }
--------------------------------------------------------------------------------
/view/src/main/kotlin/com/zedalpha/shadowgadgets/view/viewgroup/ShadowsRecyclerView.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.viewgroup
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.util.AttributeSet
6 | import android.view.View
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.zedalpha.shadowgadgets.view.ShadowPlane
9 |
10 | /**
11 | * A custom [RecyclerView] that implements [ShadowsViewGroup].
12 | *
13 | * Apart from the additional handling of the library's shadow properties and
14 | * draw operations, this group behaves just like its base class.
15 | */
16 | public class ShadowsRecyclerView @JvmOverloads constructor(
17 | context: Context,
18 | attrs: AttributeSet? = null
19 | ) : RecyclerView(context, attrs), ShadowsViewGroup {
20 |
21 | internal val manager = RecyclingManager(
22 | this,
23 | attrs,
24 | this::attachViewToParent,
25 | this::detachAllViewsFromParent,
26 | { c -> super.dispatchDraw(c) },
27 | { c, v, t -> super.drawChild(c, v, t) }
28 | )
29 |
30 | override var childShadowsPlane: ShadowPlane
31 | by manager::childShadowsPlane
32 |
33 | override var clipAllChildShadows: Boolean
34 | by manager::clipAllChildShadows
35 |
36 | override var childOutlineShadowsColorCompat: Int
37 | by manager::childOutlineShadowsColorCompat
38 |
39 | override var forceChildOutlineShadowsColorCompat: Boolean
40 | by manager::forceChildOutlineShadowsColorCompat
41 |
42 | override var ignoreInlineChildShadows: Boolean
43 | by manager::ignoreInlineChildShadows
44 |
45 | override fun onViewAdded(child: View) {
46 | super.onViewAdded(child)
47 | manager.onViewAdded(child)
48 | }
49 |
50 | override fun dispatchDraw(canvas: Canvas) {
51 | manager.dispatchDraw(canvas)
52 | }
53 |
54 | override fun drawChild(
55 | canvas: Canvas,
56 | child: View,
57 | drawingTime: Long
58 | ): Boolean = manager.drawChild(canvas, child, drawingTime)
59 | }
--------------------------------------------------------------------------------
/demo/src/main/kotlin/com/zedalpha/shadowgadgets/demo/topic/IrregularTopic.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.demo.topic
2 |
3 | import android.graphics.Path
4 | import android.os.Build
5 | import androidx.core.view.isInvisible
6 | import androidx.core.view.isVisible
7 | import com.zedalpha.shadowgadgets.demo.R
8 | import com.zedalpha.shadowgadgets.demo.databinding.FragmentIrregularBinding
9 | import com.zedalpha.shadowgadgets.view.MaterialShapeDrawableViewPathProvider
10 | import com.zedalpha.shadowgadgets.view.ViewPathProvider
11 | import com.zedalpha.shadowgadgets.view.clipOutlineShadow
12 | import com.zedalpha.shadowgadgets.view.pathProvider
13 |
14 | internal val IrregularTopic = Topic(
15 | "Irregular",
16 | R.string.description_irregular,
17 | IrregularFragment::class.java
18 | )
19 |
20 | class IrregularFragment : TopicFragment(
21 | FragmentIrregularBinding::inflate
22 | ) {
23 | override fun loadUi(ui: FragmentIrregularBinding) {
24 | if (Build.VERSION.SDK_INT < 30) {
25 | ui.caveat.text = getString(
26 | R.string.message_irregular,
27 | Build.VERSION.SDK_INT
28 | )
29 | ui.labelBroken.isInvisible = true
30 | ui.labelFixed.isInvisible = true
31 | ui.caveat.isVisible = true
32 | }
33 |
34 | ui.viewFixed.pathProvider = ViewPathProvider { v, p ->
35 | val side = v.width.toFloat()
36 | p.addRoundRect(
37 | 0F, 0F,
38 | side, side,
39 | floatArrayOf(side / 2, side / 2, 0F, 0F, 0F, 0F, 0F, 0F),
40 | Path.Direction.CW
41 | )
42 | }
43 | ui.buttonFixed.pathProvider = MaterialShapeDrawableViewPathProvider()
44 |
45 | ui.clipSwitch.setOnCheckedChangeListener { _, isChecked ->
46 | ui.viewBroken.clipOutlineShadow = isChecked
47 | ui.viewFixed.clipOutlineShadow = isChecked
48 | ui.buttonBroken.clipOutlineShadow = isChecked
49 | ui.buttonFixed.clipOutlineShadow = isChecked
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/view/lint/src/test/kotlin/com/zedalpha/shadowgadgets/view/lint/RelativeOverlapDetectorSGTest.kt:
--------------------------------------------------------------------------------
1 | package com.zedalpha.shadowgadgets.view.lint
2 |
3 | import com.android.tools.lint.checks.infrastructure.TestFiles.xml
4 | import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
5 | import org.junit.Test
6 |
7 | class RelativeOverlapDetectorSGTest {
8 |
9 | @Test
10 | fun `ShadowsRelativeLayouts warn of overlapping children`() {
11 | lint()
12 | .files(
13 | xml(
14 | "res/layout/overlap.xml",
15 | """
16 |
20 |
21 |
25 |
26 |
31 |
32 |
33 | """.trimIndent()
34 | )
35 | )
36 | .issues(RelativeOverlapDetectorSG.ISSUE_SG)
37 | .run()
38 | .expect(
39 | """
40 | res/layout/overlap.xml:11: Warning: @id/button1 can overlap @id/text1 if @id/text1, @id/button1 grow due to localized text expansion [RelativeOverlapSG]
41 |