├── .gitignore
├── .idea
├── caches
│ ├── build_file_checksums.ser
│ └── gradle_models.ser
├── codeStyles
│ └── Project.xml
├── compiler.xml
├── dictionaries
│ └── adr.xml
├── encodings.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── www
│ │ └── linwg
│ │ └── org
│ │ └── app
│ │ ├── BottomBookStyleSettingFragment.kt
│ │ ├── BottomMeshSettingFragment.kt
│ │ ├── CardBgSettingFragment.kt
│ │ ├── CardListActivity.java
│ │ ├── CardListFragment.kt
│ │ ├── CornerSettingFragment.kt
│ │ ├── CornerSyncFragment.kt
│ │ ├── DemoViewModel.kt
│ │ ├── MainActivity.kt
│ │ ├── OnSeekBarChangeAdapter.java
│ │ ├── ShadowColorSettingFragment.kt
│ │ ├── ShadowOffsetSettingFragment.kt
│ │ ├── ShadowShapeSettingFragment.kt
│ │ ├── ShadowSizeSettingFragment.kt
│ │ └── TestActivity.java
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_launcher_background.xml
│ ├── indicator.xml
│ └── test.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── activity_test.xml
│ ├── activity_test1.xml
│ ├── fragment_bottom_book_style_setting.xml
│ ├── fragment_bottom_mesh_setting.xml
│ ├── fragment_card_bg_setting.xml
│ ├── fragment_card_list.xml
│ ├── fragment_corner_setting.xml
│ ├── fragment_corner_sync.xml
│ ├── fragment_shadow_color_setting.xml
│ ├── fragment_shadow_offset_setting.xml
│ ├── fragment_shadow_shape_setting.xml
│ ├── fragment_shadow_size_setting.xml
│ ├── item_test.xml
│ ├── item_test2.xml
│ └── item_test3.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── girl.webp
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── refs.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── kotlin
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── www
│ │ └── linwg
│ │ └── org
│ │ └── lib
│ │ ├── IShadow.kt
│ │ ├── LCardView.kt
│ │ ├── LinearShadow.kt
│ │ ├── RadialShadow.kt
│ │ ├── ShadowManager.kt
│ │ └── ShadowPool.kt
│ └── res
│ └── values
│ └── attrs.xml
├── lib
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── www
│ │ └── linwg
│ │ └── org
│ │ └── lib
│ │ └── LCardView.java
│ └── res
│ └── values
│ └── attrs.xml
├── screenshot
├── book.gif
├── cn.gif
├── color.gif
├── demo.apk
├── ele.gif
├── list.gif
├── mesh.gif
├── offset.gif
└── sync.gif
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 | sering.gpg
12 | /.idea/kotlinc.xml
13 | /.idea/deploymentTargetSelector.xml
14 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/caches/gradle_models.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/.idea/caches/gradle_models.ser
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/dictionaries/adr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | lcardview
5 | linwg
6 | verts
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/.idea/misc.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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LCardView
2 | 卡片布局,可设置阴影颜色,透明度,圆角大小,阴影宽度,阴影偏移量,卡片圆角/阴影圆角独立设置,特殊阴影效果,底部阴影扭曲效果等。
3 |
4 | ### Java gradle dependencies:
5 | ~~~groovy
6 | dependencies {
7 | implementation 'org.linwg1988:lcardview:1.5.4'
8 | }
9 | ~~~
10 |
11 | ### Kotlin gradle dependencies:
12 | ~~~groovy
13 | dependencies {
14 | implementation 'io.github.linwg1988:lcardview-kt:1.0.4'
15 | }
16 | ~~~
17 |
18 | Kotlin 版本1.0.2 新增了部分属性(描边,渐变色等),可有效减少shape.xml的创建
19 | 现在卡片式的设计还是比较常见的,设计师们常常天马行空的设计出各种好看(emmm)的的卡片样式,但是安卓原生的CardView的局限性还是比较大的,比如没办法设置阴影的颜色,阴影的透明度等等等等;那既要满足设计师们的要求且又不往包里面添加.9图片使,那可以试试这款卡片布局。demo.apk
20 |
21 | LCardView继承FrameLayout,使用方式与之并没有什么太大区别,下面一些动图将展示LCardView具有哪些功能:
22 |
23 | #### cornerRadius
24 | * 可单独设置也可同时设置圆角半径。
25 |
26 |
27 |
28 | ### shadowOffset
29 | * 可单独或同时设置4边的偏移量,偏移量为正时往外偏移,最大值为阴影宽度的一半,偏移量为负时往内偏移。
30 |
31 |
32 |
33 | ### leftShadowDecrement & topShadowDecrement & rightShadowDecrement & bottomShadowDecrement
34 | * 可设置4边阴影的缩减尺寸,最大值为阴影宽度,该设置不会影响阴影所在的位置。
35 |
36 | ### cardBackground
37 | * 卡片背景,和cardBackgroundColor相比,可以设置图片或者Drawable文件。
38 |
39 | ### gradientColors
40 | * 卡片背景渐变色,xml中以英文逗号分隔,可避免创建drawable文件。
41 |
42 | ### gradientDirection
43 | * 卡片背景渐变色方向。
44 |
45 | ### gradientSizeFollowView
46 | * 卡片背景渐变色尺寸是否与控件尺寸一致(斜方向时配置),若false,渐变色的形状大小为边长为最大长宽的一个正方形。
47 |
48 | ### shadowColor/shadowAlpha
49 | * 阴影的颜色与透明度分开设置,所以设置 **shadowColor(color:Int)** 时参数 color 的 alpha 是不起作用的,要设置透明度需使用 **setShadowAlpha(alpha:Int)** 。
50 |
51 |
52 |
53 | ### elevation
54 | * 顾名思义卡片高度,此参数可作用于透明度以及阴影宽度,改参数不改变 View 本身的 elevation 属性。
55 |
56 |
57 |
58 | ### paperSyncCorner & paperCorner
59 | * 有时候卡片本身我们不想设置圆角,但是我们(**真的是我们而不是设计师?**)又希望阴影的圆角比较大,这个属性就起作用了,更进一步你可以为卡片和阴影分别设置不同的圆角半径(相信应该没有人会觉得卡片半径大于阴影半径好看吧)。
60 | * **现仅 kotlin 库支持**
61 |
62 |
63 |
64 | ### linearBookEffect & bookRadius
65 | * emmm,要怎么说呢,还是看图吧,图片看不了的话就下载一个 demo 看看效果吧,如果对这个效果感兴趣的话。
66 | * **现仅 kotlin 库支持**
67 |
68 |
69 |
70 | ### curveShadowEffect & curvature
71 | * 这个效果还是蛮酷的,使卡片更加具有立体感了,我个人灰常中意它。
72 | * **现仅 kotlin 库支持**
73 |
74 |
75 |
76 | ### useShadowPool & bindLifeCircle
77 | * 这两个属性适用于同样式卡片的列表,如果启用 **shadowPool** 缓存,着色器与 Bitmap 只会被第一张卡片创建,其余的都将复用缓存池中的对象,减少内存开销,由于缓存对象位于静态池中,页面销毁时需要解除卡片的缓存池对象引用,推荐使用 **bindLifeCircle** 属性使卡片与页面邦定,使页面销毁时自动移除引用,但是如果没有绑定生命周期也没有关系,LCardView 在 attach 与 detach 时会自动建立/移除引用关系(或许增加了查询开销??)。
78 | * **现仅 kotlin 库支持**
79 |
80 |
81 |
82 | ### other
83 | * **propterties()** 提供了一个可以链式设置卡片多种属性的方案,只在最后一次设置属性时重建阴影并重绘,减少对象创建优化了内存开销。
84 | * **fixedContentWidth/fixedContentHeight** 改变卡片的测量方式,底下版本日志有解释,比较不常用就不多说了。
85 | * 以上所有属性大可同时设置,撸出你(or设计师)想要的效果,放心的让设计师随便改阴影了,我们完全不慌。
86 |
87 | 属性说明:
88 |
89 | | xml属性名称 | 中文释义 |
90 | |---------------------------------|----------------------------|
91 | | attr:shadowSize | 四边阴影宽度 |
92 | | attr:shadowStartAlpha | 阴影颜色初始透明度 |
93 | | attr:shadowFluidShape | 阴影流动形状(线性/吸附) |
94 | | attr:shadowColor | 阴影颜色RGB值(透明度此处无效) |
95 | | attr:cardBackgroundColor | 卡片背景色 |
96 | | attr:cornerRadius | 阴影圆角半径 |
97 | | attr:leftTopCornerRadius | 左上圆角半径 |
98 | | attr:rightTopCornerRadius | 右上圆角半径 |
99 | | attr:leftBottomCornerRadius | 左下圆角半径 |
100 | | attr:rightBottomCornerRadius | 右下圆角半径 |
101 | | attr:elevation | 卡片高度 |
102 | | attr:elevationAffectShadowColor | 卡片高度是否影响阴影颜色 |
103 | | attr:elevationAffectShadowSize | 卡片高度是否影响阴影宽度 |
104 | | attr:leftOffset | 卡片左半区阴影偏移量 |
105 | | attr:rightOffset | 卡片右半区阴影偏移量 |
106 | | attr:topOffset | 卡片上半区阴影偏移量 |
107 | | attr:bottomOffset | 卡片右半区阴影偏移量 |
108 | | attr:fixedContentWidth | 控件宽度是否固定为内容宽度 |
109 | | attr:fixedContentHeight | 控件高度是否固定为内容高度 |
110 | | attr:paperSyncCorner | 同步卡片圆角与阴影圆角大小 |
111 | | attr:paperCorner | 卡片圆角半径 |
112 | | attr:linearBookEffect | 线性书本阴影效果 |
113 | | attr:bookRadius | 线性书本阴影偏移倍率(底部阴影宽度) |
114 | | attr:curveShadowEffect | 底部阴影扭曲效果 |
115 | | attr:curvature | 底部阴影扭曲率 |
116 | | attr:useShadowPool | 是否启用阴影缓存池 |
117 | | attr:bindLifeCircle | 是否绑定生命周期 |
118 | | attr:cardBackground | 卡片背景 |
119 | | attr:gradientColors | 卡片背景渐变色 |
120 | | attr:gradientSizeFollowView | 卡片背景渐变色尺寸是否与控件尺寸一致(斜方向时配置) |
121 | | attr:gradientDirection | 卡片背景渐变色方向 |
122 | | attr:strokeWidth | 描边尺寸 |
123 | | attr:strokeColor | 描边颜色 |
124 | | attr:leftShadowDecrement | 左侧阴影缩减尺寸 |
125 | | attr:topShadowDecrement | 顶部阴影缩减尺寸 |
126 | | attr:rightShadowDecrement | 右侧阴影缩减尺寸 |
127 | | attr:bottomShadowDecrement | 底部阴影缩减尺寸 |
128 |
129 | ## Change Logs.
130 |
131 | ### Kotlin版本
132 |
133 | ### 1.0.4
134 | * bug 修复:修复底部阴影在卡片高度变化时重建的情况;
135 | * 因shadowDecrement参数影响,阴影实际绘制的区域如果为空或被其它内容覆盖,则不参与绘制过程
136 |
137 | ### 1.0.3
138 | * 一些 bug 修复,圆角精度改为浮点类型,最大圆角自动修正为最小宽高的一半,避免阴影重叠;
139 | * 底部扭曲不在限制阴影形状;
140 | * 阴影 bitmap 缓存池修改,最大限制从个数改为实际占用内存;
141 | * 新增属性 leftShadowDecrement,topShadowDecrement,rightShadowDecrement,bottomShadowDecrement 可对四边阴影的宽度进行缩减,
142 | * 相对于 showDowOffset 属性来说,阴影宽度的缩减不会导致阴影位置的变化,但会对阴影的圆角进行扭曲;
143 | * 调整 bookRadius 属性,设置后底部阴影中间会在往上扭曲(阴影宽度 size * bookRadius),并往两侧线性递减
144 |
145 | ### 1.0.2
146 | * bug修复;
147 |
148 | ### 1.0.1
149 | * 新增卡片背景属性cardBackground,使用方式与android:background一致,cardBackground只会在卡片的内容区域绘制;
150 | * 新增描边属性(strokeColor & strokeWidth),背景渐变色属性(gradientColors & gradientDirection & gradientSizeFollowView)
151 | * 便于直接创建描边控件,减少项目中xml的创建;
152 | * 背景属性的优先级 cardBackgroundColor > gradientColors > cardBackground;
153 | * 修复在部分场景下出现的锯齿情况或者阴影重叠或描边缺失的问题;
154 |
155 | ### 0.0.1
156 | * 新增卡片圆角与阴影圆角是否同步的属性,自由度更高
157 | * 新增底部线性类似于书本阴影的效果(不要吐槽命名)
158 | * 新增底部类似扭曲阴影的效果(同上,不要吐槽)
159 | * 新增 **properties()** 方法,链式设置多个属性,单次修改阴影实例
160 | * 去除部分场景下的无效绘制,优化绘制速度
161 | * 新增卡片列表使用时阴影重复使用的支持,依赖于缓存池,且自动绑定生命周期
162 |
163 | ### Java版本
164 | ### 1.5.4
165 | * 新增属性 fixedContentWidth,fixedContentHeight.使用场景:卡片布局的父布局因为动画需要大小动态变化来对卡片布局进行隐藏或显示,
166 | * 若卡片尺寸是根据内部子控件大小来获得,卡片尺寸属性设置为 wrap_content,此时卡片的父布局动态修改尺寸时会导致卡片重新测量大小。该属
167 | * 性值为 true 时,父布局的大小不影响卡片的测量内部子控件的结果,故而也不会触发阴影重新创建以及内容的裁切大小。
168 |
169 | ### 1.5.2
170 | * 优化阴影创建的条件,只有在参数变化时才重新创建,修复View在没有测量完成时设置卡片属性导致的阴影异常,去除setShadowOffset()方法。
171 |
172 | ### 1.5.0
173 | * 此版本已经弃用四边阴影宽度分别设置的方法。
174 | * 对于阴影偏移的实现进行修改替换,分为上、下、左、右四个区域,
175 | * 偏移的数值为正,则从卡片中心向外偏移,
176 | * 偏移的数值为负,则从卡片中心向内偏移,
177 | * 你可以使用: **setShadowOffsetCenter(offset)** 进行以卡片中心的整体偏移。
178 |
179 | ### 1.4.2
180 | * 修复因为偏移属性的增加导致阴影透明度影响背景色的问题
181 | * 控件存在的局限性:四边阴影大小不一的时候无法设置圆角;
182 | * 同理设置圆角大小的时候四边的阴影大小会自动恢复成初始值
183 |
184 | ### 1.4.1
185 | * 修复Android P圆角失效的问题
186 |
187 | ### 1.4
188 | * 增加了X轴和Y轴偏移量属性,暂时只允许偏移至边缘位置。
189 |
190 | ### 1.2
191 | * 新增了一些xml初始化属性,让布局在xml中更加直观。
192 |
193 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | namespace "www.linwg.org.app"
8 |
9 | compileSdk 33
10 |
11 | defaultConfig {
12 | applicationId "www.linwg.org.app"
13 | minSdk 21
14 | targetSdk 33
15 | versionCode 1
16 | versionName "1.0"
17 | }
18 |
19 | compileOptions {
20 | sourceCompatibility JavaVersion.VERSION_1_8
21 | targetCompatibility JavaVersion.VERSION_1_8
22 | }
23 | kotlinOptions {
24 | jvmTarget = '1.8'
25 | }
26 | buildTypes {
27 | release {
28 | minifyEnabled true
29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
30 | }
31 | debug {
32 | minifyEnabled false
33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34 | }
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(include: ['*.jar'], dir: 'libs')
40 | implementation 'androidx.appcompat:appcompat:1.5.0'
41 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
42 | implementation 'com.google.android.material:material:1.5.0'
43 | implementation 'androidx.cardview:cardview:1.0.0'
44 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
45 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
46 | implementation 'androidx.lifecycle:lifecycle-livedata-core:2.5.0'
47 | implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.0'
48 | implementation project(':kotlin')
49 | }
50 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/BottomBookStyleSettingFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.CheckBox
8 | import android.widget.SeekBar
9 | import androidx.fragment.app.Fragment
10 | import androidx.lifecycle.ViewModelProvider
11 | import www.linwg.org.lib.LCardView
12 |
13 | class BottomBookStyleSettingFragment : Fragment() {
14 |
15 | private val viewModel: DemoViewModel by lazy {
16 | ViewModelProvider(requireActivity())[DemoViewModel::class.java]
17 | }
18 |
19 | override fun onCreateView(
20 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
21 | ): View? {
22 | return inflater.inflate(R.layout.fragment_bottom_book_style_setting, container, false)
23 | }
24 |
25 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
26 | val sbAngle = view.findViewById(R.id.sbAngle)
27 | val cbBook = view.findViewById(R.id.cbBook)
28 | cbBook.setOnCheckedChangeListener { _, isChecked ->
29 | viewModel.linearBookEffect.value = isChecked
30 | }
31 | viewModel.curveShadowEffect.observe(viewLifecycleOwner) {
32 | if (it) {
33 | cbBook.isChecked = false
34 | }
35 | }
36 | sbAngle.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
37 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
38 | viewModel.bookRadius.value = progress / 100f
39 | }
40 | })
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/BottomMeshSettingFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.CheckBox
8 | import android.widget.SeekBar
9 | import androidx.fragment.app.Fragment
10 | import androidx.lifecycle.ViewModelProvider
11 |
12 | class BottomMeshSettingFragment : Fragment() {
13 |
14 | private val viewModel: DemoViewModel by lazy {
15 | ViewModelProvider(requireActivity())[DemoViewModel::class.java]
16 | }
17 |
18 | override fun onCreateView(
19 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
20 | ): View? {
21 | return inflater.inflate(R.layout.fragment_bottom_mesh_setting, container, false)
22 | }
23 |
24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
25 | val cbMesh = view.findViewById(R.id.cbMesh)
26 | val sbCur = view.findViewById(R.id.sbCur)
27 | cbMesh.setOnCheckedChangeListener { _, isChecked ->
28 | viewModel.curveShadowEffect.value = isChecked
29 | }
30 | viewModel.linearBookEffect.observe(viewLifecycleOwner) {
31 | if (it) {
32 | cbMesh.isChecked = false
33 | }
34 | }
35 | sbCur.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
36 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
37 | viewModel.curvature.value = progress / 10f
38 | }
39 | })
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/CardBgSettingFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.graphics.Color
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.CheckBox
9 | import android.widget.RadioGroup
10 | import android.widget.SeekBar
11 | import android.widget.TextView
12 | import androidx.core.content.res.ResourcesCompat
13 | import androidx.fragment.app.Fragment
14 | import androidx.lifecycle.ViewModelProvider
15 | import www.linwg.org.lib.LCardView
16 |
17 | class CardBgSettingFragment : Fragment() {
18 |
19 | private val viewModel: DemoViewModel by lazy {
20 | ViewModelProvider(requireActivity())[DemoViewModel::class.java]
21 | }
22 |
23 | override fun onCreateView(
24 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
25 | ): View? {
26 | return inflater.inflate(R.layout.fragment_card_bg_setting, container, false)
27 | }
28 |
29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
30 | val sbSW: SeekBar = view.findViewById(R.id.sbSW)
31 | sbSW.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
32 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
33 | viewModel.strokeWidth.value = progress
34 | }
35 | })
36 | val cbRes: CheckBox = view.findViewById(R.id.cbRes)
37 | cbRes.setOnCheckedChangeListener { _, isChecked ->
38 | viewModel.cardBackgroundDrawableRes.value = if (isChecked) R.mipmap.girl else 0
39 | }
40 | val cbDrawable: CheckBox = view.findViewById(R.id.cbDrawable)
41 | cbDrawable.setOnCheckedChangeListener { _, isChecked ->
42 | viewModel.cardBackground.value = if (isChecked) ResourcesCompat.getDrawable(
43 | resources,
44 | R.drawable.test,
45 | null
46 | ) else null
47 | }
48 |
49 | val tvLabel: TextView = view.findViewById(R.id.tvLabel)
50 | val rgGradientDirection: RadioGroup =
51 | view.findViewById(R.id.rgGradientDirection)
52 | val rgStart: RadioGroup = view.findViewById(R.id.rgStart)
53 | val rgCenter: RadioGroup = view.findViewById(R.id.rgCenter)
54 | val rgEnd: RadioGroup = view.findViewById(R.id.rgEnd)
55 | val llColor: View = view.findViewById(R.id.llColor)
56 | val cbGradient: CheckBox = view.findViewById(R.id.cbGradient)
57 | val cbGradientSync: CheckBox = view.findViewById(R.id.cbGradientSync)
58 | cbGradient.setOnCheckedChangeListener { _, isChecked ->
59 | tvLabel.visibility = if (isChecked) View.VISIBLE else View.GONE
60 | rgGradientDirection.visibility = if (isChecked) View.VISIBLE else View.GONE
61 | llColor.visibility = if (isChecked) View.VISIBLE else View.GONE
62 | if (!isChecked) {
63 | viewModel.gradientColors.value = intArrayOf()
64 | }
65 | }
66 | cbGradientSync.setOnCheckedChangeListener { _, isChecked ->
67 | viewModel.gradientSizeFollowView.value = isChecked
68 | }
69 | rgGradientDirection.setOnCheckedChangeListener { _, checkedId ->
70 | viewModel.gradientDirection.value = when (checkedId) {
71 | R.id.rbLTR -> LCardView.LEFT_TO_RIGHT
72 | R.id.rbTTB -> LCardView.TOP_TO_BOTTOM
73 | R.id.rbLTTRB -> LCardView.LEFT_TOP_TO_RIGHT_BOTTOM
74 | else -> LCardView.LEFT_BOTTOM_TO_RIGHT_TOP
75 | }
76 | }
77 | rgStart.setOnCheckedChangeListener { _, checkedId ->
78 | val endId = rgEnd.checkedRadioButtonId
79 | val centerId = rgCenter.checkedRadioButtonId
80 | val startColor =
81 | if (checkedId == R.id.rbRedStart) Color.RED else if (checkedId == R.id.rbGreenStart) Color.GREEN else Color.BLUE
82 | val endColor =
83 | if (endId == R.id.rbRedEnd) Color.WHITE else if (endId == R.id.rbGrayEnd) Color.GRAY else Color.BLACK
84 | val centerColor = when (centerId) {
85 | R.id.rbRedCenter -> Color.YELLOW
86 | R.id.rbGreenCenter -> Color.parseColor(
87 | "#ff00ff"
88 | )
89 | else -> Color.parseColor("#00ffff")
90 | }
91 | viewModel.gradientColors.value = intArrayOf(startColor, centerColor, endColor)
92 | }
93 | rgCenter.setOnCheckedChangeListener { group, checkedId ->
94 | val endId = rgEnd.checkedRadioButtonId
95 | val startId = rgStart.checkedRadioButtonId
96 | val startColor =
97 | if (startId == R.id.rbRedStart) Color.RED else if (startId == R.id.rbGreenStart) Color.GREEN else Color.BLUE
98 | val endColor =
99 | if (endId == R.id.rbRedEnd) Color.WHITE else if (endId == R.id.rbGrayEnd) Color.GRAY else Color.BLACK
100 | val centerColor = when (checkedId) {
101 | R.id.rbRedCenter -> Color.YELLOW
102 | R.id.rbGreenCenter -> Color.parseColor(
103 | "#ff00ff"
104 | )
105 | else -> Color.parseColor("#00ffff")
106 | }
107 | viewModel.gradientColors.value = intArrayOf(startColor, centerColor, endColor)
108 | }
109 | rgEnd.setOnCheckedChangeListener { _, checkedId ->
110 | val centerId = rgCenter.checkedRadioButtonId
111 | val startId = rgStart.checkedRadioButtonId
112 | val startColor =
113 | if (startId == R.id.rbRedStart) Color.RED else if (startId == R.id.rbGreenStart) Color.GREEN else Color.BLUE
114 | val endColor =
115 | if (checkedId == R.id.rbRedEnd) Color.WHITE else if (checkedId == R.id.rbGrayEnd) Color.GRAY else Color.BLACK
116 | val centerColor = when (centerId) {
117 | R.id.rbRedCenter -> Color.YELLOW
118 | R.id.rbGreenCenter -> Color.parseColor(
119 | "#ff00ff"
120 | )
121 | else -> Color.parseColor("#00ffff")
122 | }
123 | viewModel.gradientColors.value = intArrayOf(startColor, centerColor, endColor)
124 | }
125 | }
126 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/CardListActivity.java:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ImageView;
9 | import android.widget.TextView;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.Nullable;
13 | import androidx.appcompat.app.AppCompatActivity;
14 | import androidx.recyclerview.widget.LinearLayoutManager;
15 | import androidx.recyclerview.widget.RecyclerView;
16 |
17 | import www.linwg.org.lib.LCardView;
18 |
19 | public class CardListActivity extends AppCompatActivity {
20 |
21 | @Override
22 | protected void onCreate(@Nullable Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_test);
25 | RecyclerView recyclerView = findViewById(R.id.recyclerView);
26 | recyclerView.setLayoutManager(new LinearLayoutManager(this));
27 | recyclerView.setAdapter(new Ad(this));
28 | }
29 |
30 | private static class Ad extends RecyclerView.Adapter {
31 | private final Context context;
32 | private final String[] arr = {
33 | "属性说明:1.useShadowPool为true时,列表中的着色器以及Bitmap将储存于缓存池中一起使用",
34 | "如果一个列表中的卡片样式都一致的话,建议开启此功能",
35 | "2.开启缓存池后,建议同时设置bindLifeCircle为true",
36 | "bindLifeCircle属性将自动帮助卡片绑定生命周期,避免共用缓存池对象导致内存泄漏",
37 | "3.如果没有设置bindLifeCircle也没有关系,每张卡片在attach或detach的时候会自动清除缓存池对象的引用(只在useShadowPool为true时有效)",
38 | "个人估计,理论上这样会增加一点点点点点的查询开销.",
39 | "其他属性说明:fixedContentWidth/fixedContentHeight",
40 | "启用这两个属性时卡片的measure结果将会被改变,当卡片与卡片父布局的尺寸都为wrap_content时,使用fixedContentWidth,卡片的尺寸由子view的内容宽高计算获得,否则为0(原计算结果)",
41 | "卡片properties()方法用于一次性设置多个属性,避免重复创建着色器或Bitmap."
42 | };
43 |
44 | private Ad(Context context) {
45 | this.context = context;
46 | }
47 |
48 | @Override
49 | public int getItemViewType(int position) {
50 | return 0;
51 | }
52 |
53 | @NonNull
54 | @Override
55 | public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
56 | return new VH(LayoutInflater.from(context).inflate(R.layout.item_test, parent, false));
57 | }
58 |
59 | @Override
60 | public void onBindViewHolder(final VH holder, int position) {
61 | //ignore
62 | holder.testView.setText(arr[position% arr.length ]);
63 | }
64 |
65 | @Override
66 | public int getItemCount() {
67 | return 1000;
68 | }
69 | }
70 |
71 | private static class VH extends RecyclerView.ViewHolder {
72 | ImageView imageView;
73 | TextView testView;
74 | LCardView cardView;
75 |
76 | public VH(View itemView) {
77 | super(itemView);
78 | imageView = itemView.findViewById(R.id.imageView);
79 | testView = itemView.findViewById(R.id.testView);
80 | cardView = itemView.findViewById(R.id.cardView);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/CardListFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.Fragment
9 |
10 | class CardListFragment: Fragment() {
11 |
12 | override fun onCreateView(
13 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
14 | ): View? {
15 | return inflater.inflate(R.layout.fragment_card_list, container, false)
16 | }
17 |
18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
19 | view.findViewById(R.id.tvToList1).setOnClickListener { startActivity(Intent(requireActivity(), CardListActivity::class.java)) }
20 | view.findViewById(R.id.tvToList2).setOnClickListener { startActivity(Intent(requireActivity(), TestActivity::class.java)) }
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/CornerSettingFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.SeekBar
8 | import androidx.fragment.app.Fragment
9 | import androidx.lifecycle.ViewModelProvider
10 |
11 | class CornerSettingFragment: Fragment() {
12 |
13 | private val viewModel :DemoViewModel by lazy {
14 | ViewModelProvider(requireActivity())[DemoViewModel::class.java]
15 | }
16 |
17 | override fun onCreateView(
18 | inflater: LayoutInflater,
19 | container: ViewGroup?,
20 | savedInstanceState: Bundle?
21 | ): View? {
22 | return inflater.inflate(R.layout.fragment_corner_setting, container, false)
23 | }
24 |
25 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
26 | val sbLT = view.findViewById(R.id.sbLT)
27 | val sbRT = view.findViewById(R.id.sbRT)
28 | val sbRB = view.findViewById(R.id.sbRB)
29 | val sbLB = view.findViewById(R.id.sbLB)
30 | val sbAll = view.findViewById(R.id.sbAll)
31 | sbLT.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
32 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
33 | viewModel.leftTopCornerRadius.value = progress.toFloat()
34 | }
35 | })
36 | sbRT.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
37 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
38 | viewModel.rightTopCornerRadius.value = progress.toFloat()
39 | }
40 | })
41 | sbLB.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
42 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
43 | viewModel.leftBottomCornerRadius.value = progress.toFloat()
44 | }
45 | })
46 | sbRB.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
47 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
48 | viewModel.rightBottomCornerRadius.value = progress.toFloat()
49 | }
50 | })
51 | sbAll.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
52 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
53 | viewModel.cornerRadius.value = progress.toFloat()
54 | }
55 | })
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/CornerSyncFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.CheckBox
8 | import android.widget.SeekBar
9 | import androidx.fragment.app.Fragment
10 | import androidx.lifecycle.ViewModelProvider
11 |
12 | class CornerSyncFragment: Fragment() {
13 |
14 | private val viewModel :DemoViewModel by lazy {
15 | ViewModelProvider(requireActivity())[DemoViewModel::class.java]
16 | }
17 |
18 | override fun onCreateView(
19 | inflater: LayoutInflater,
20 | container: ViewGroup?,
21 | savedInstanceState: Bundle?
22 | ): View? {
23 | return inflater.inflate(R.layout.fragment_corner_sync, container, false)
24 | }
25 |
26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
27 | val cbSync = view.findViewById(R.id.cbSync)
28 | val sbCardCN = view.findViewById(R.id.sbCardCN)
29 | cbSync.setOnCheckedChangeListener { _, isChecked ->
30 | viewModel.paperSyncCorner.value = isChecked
31 | }
32 | sbCardCN.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
33 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
34 | viewModel.paperCorner.value = progress.toFloat()
35 | }
36 | })
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/DemoViewModel.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.graphics.drawable.Drawable
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 |
7 | class DemoViewModel:ViewModel() {
8 | val shadowFluidShape = MutableLiveData()
9 | val shadowColor = MutableLiveData()
10 | val shadowStartAlpha = MutableLiveData()
11 | val shadowSize = MutableLiveData()
12 | val leftWidthDecrement = MutableLiveData()
13 | val topWidthDecrement = MutableLiveData()
14 | val rightWidthDecrement = MutableLiveData()
15 | val bottomWidthDecrement = MutableLiveData()
16 | val leftOffset = MutableLiveData()
17 | val topOffset = MutableLiveData()
18 | val rightOffset = MutableLiveData()
19 | val bottomOffset = MutableLiveData()
20 | val shadowOffsetCenter = MutableLiveData()
21 | val cornerRadius = MutableLiveData()
22 | val paperCorner = MutableLiveData()
23 | val leftTopCornerRadius = MutableLiveData()
24 | val rightTopCornerRadius = MutableLiveData()
25 | val rightBottomCornerRadius = MutableLiveData()
26 | val leftBottomCornerRadius = MutableLiveData()
27 | val paperSyncCorner = MutableLiveData()
28 | val curveShadowEffect = MutableLiveData()
29 | val linearBookEffect = MutableLiveData()
30 | val gradientSizeFollowView = MutableLiveData()
31 | val curvature = MutableLiveData()
32 | val strokeWidth = MutableLiveData()
33 | val bookRadius = MutableLiveData()
34 | val gradientDirection = MutableLiveData()
35 | val cardBackgroundDrawableRes = MutableLiveData()
36 | val cardBackground = MutableLiveData()
37 | val gradientColors = MutableLiveData()
38 | val leftDe = MutableLiveData()
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.graphics.Color
4 | import android.os.Bundle
5 | import android.view.Gravity
6 | import android.widget.TextView
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.appcompat.widget.AppCompatTextView
9 | import androidx.fragment.app.Fragment
10 | import androidx.lifecycle.ViewModelProvider
11 | import androidx.viewpager2.adapter.FragmentStateAdapter
12 | import androidx.viewpager2.widget.ViewPager2
13 | import com.google.android.material.tabs.TabLayout
14 | import www.linwg.org.lib.LCardView
15 |
16 | class MainActivity : AppCompatActivity() {
17 | private val viewModel :DemoViewModel by lazy {
18 | ViewModelProvider(this)[DemoViewModel::class.java]
19 | }
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_main)
24 | val cardBg = findViewById(R.id.cardView)
25 |
26 | viewModel.shadowFluidShape.observe(this){
27 | cardBg.setShadowFluidShape(it)
28 | }
29 | viewModel.shadowStartAlpha.observe(this){
30 | cardBg.setShadowAlpha(it)
31 | }
32 | viewModel.shadowColor.observe(this){
33 | cardBg.setShadowColor(it)
34 | }
35 | viewModel.shadowSize.observe(this){
36 | cardBg.setShadowSize(it)
37 | }
38 | viewModel.leftOffset.observe(this){
39 | cardBg.setLeftOffset(it)
40 | }
41 | viewModel.rightOffset.observe(this){
42 | cardBg.setRightOffset(it)
43 | }
44 | viewModel.topOffset.observe(this){
45 | cardBg.setTopOffset(it)
46 | }
47 | viewModel.bottomOffset.observe(this){
48 | cardBg.setBottomOffset(it)
49 | }
50 | viewModel.shadowOffsetCenter.observe(this){
51 | cardBg.setShadowOffsetCenter(it)
52 | }
53 | viewModel.leftTopCornerRadius.observe(this){
54 | cardBg.setLeftTopCornerRadius(it)
55 | }
56 | viewModel.rightTopCornerRadius.observe(this){
57 | cardBg.setRightTopCornerRadius(it)
58 | }
59 | viewModel.rightBottomCornerRadius.observe(this){
60 | cardBg.setRightBottomCornerRadius(it)
61 | }
62 | viewModel.leftBottomCornerRadius.observe(this){
63 | cardBg.setLeftBottomCornerRadius(it)
64 | }
65 | viewModel.cornerRadius.observe(this){
66 | cardBg.setCornerRadius(it)
67 | }
68 | viewModel.paperCorner.observe(this){
69 | cardBg.setPaperCorner(it)
70 | }
71 | viewModel.paperSyncCorner.observe(this){
72 | cardBg.setPaperSyncCorner(it)
73 | }
74 | viewModel.curveShadowEffect.observe(this){
75 | cardBg.setCurveShadowEffect(it)
76 | }
77 | viewModel.curvature.observe(this){
78 | cardBg.setCurvature(it)
79 | }
80 | viewModel.linearBookEffect.observe(this){
81 | cardBg.setLinearBookEffect(it)
82 | }
83 | viewModel.bookRadius.observe(this){
84 | cardBg.setBookRadius(it)
85 | }
86 | viewModel.gradientSizeFollowView.observe(this){
87 | cardBg.setGradientSizeFollowView(it)
88 | }
89 | viewModel.gradientDirection.observe(this){
90 | cardBg.setGradientDirection(it)
91 | }
92 | viewModel.gradientColors.observe(this){
93 | cardBg.setGradientColors(*it)
94 | }
95 | viewModel.strokeWidth.observe(this){
96 | cardBg.setStrokeWidth(it)
97 | }
98 | viewModel.cardBackgroundDrawableRes.observe(this){
99 | cardBg.setCardBackgroundDrawableRes(it)
100 | }
101 | viewModel.cardBackground.observe(this){
102 | cardBg.setCardBackground(it)
103 | }
104 | viewModel.leftWidthDecrement.observe(this){
105 | cardBg.setLeftShadowDecrement(it)
106 | }
107 | viewModel.topWidthDecrement.observe(this){
108 | cardBg.setTopShadowDecrement(it)
109 | }
110 | viewModel.rightWidthDecrement.observe(this){
111 | cardBg.setRightShadowDecrement(it)
112 | }
113 | viewModel.bottomWidthDecrement.observe(this){
114 | cardBg.setBottomShadowDecrement(it)
115 | }
116 | viewModel.shadowSize.value = cardBg.getShadowSize()
117 |
118 | val tabLayout = findViewById(R.id.tabLayout)
119 | val viewPager = findViewById(R.id.viewPager)
120 | tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
121 |
122 | val fragments = ArrayList()
123 | fragments.add(CornerSettingFragment())
124 | fragments.add(BottomMeshSettingFragment())
125 | fragments.add(BottomBookStyleSettingFragment())
126 | fragments.add(CornerSyncFragment())
127 | fragments.add(ShadowOffsetSettingFragment())
128 | fragments.add(ShadowShapeSettingFragment())
129 | fragments.add(ShadowSizeSettingFragment())
130 | fragments.add(ShadowColorSettingFragment())
131 | fragments.add(CardBgSettingFragment())
132 | fragments.add(CardListFragment())
133 |
134 | addTab("圆角设置",tabLayout)
135 | addTab("底部扭曲阴影设置",tabLayout)
136 | addTab("底部书本阴影设置",tabLayout)
137 | addTab("圆角同步设置",tabLayout)
138 | addTab("阴影偏移设置",tabLayout)
139 | addTab("阴影形状设置",tabLayout)
140 | addTab("阴影大小设置",tabLayout)
141 | addTab("阴影颜色设置",tabLayout)
142 | addTab("卡片背景设置",tabLayout)
143 | addTab("卡片列表",tabLayout)
144 |
145 | viewPager.offscreenPageLimit = 10
146 | viewPager.adapter = object:FragmentStateAdapter(this){
147 | override fun getItemCount(): Int {
148 | return 10
149 | }
150 |
151 | override fun createFragment(position: Int): Fragment {
152 | return fragments[position]
153 | }
154 | }
155 |
156 | tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
157 | override fun onTabSelected(tab: TabLayout.Tab) {
158 | tab.customView?.findViewById(android.R.id.text1)?.setTextColor(Color.parseColor("#3269F6"))
159 | viewPager.setCurrentItem(tab.position, false)
160 | }
161 |
162 | override fun onTabUnselected(tab: TabLayout.Tab) {
163 | tab.customView?.findViewById(android.R.id.text1)?.setTextColor(Color.parseColor("#212121"))
164 | }
165 | override fun onTabReselected(tab: TabLayout.Tab) {}
166 | })
167 | viewPager.registerOnPageChangeCallback(object :ViewPager2.OnPageChangeCallback(){
168 | override fun onPageSelected(position: Int) {
169 | tabLayout.getTabAt(position)?.select()
170 | }
171 | })
172 | }
173 |
174 | private fun addTab(s: String, tabLayout: TabLayout) {
175 | val tab = tabLayout.newTab()
176 | tab.tag = s
177 | tab.customView = AppCompatTextView(this).apply {
178 | id = android.R.id.text1
179 | text = s
180 | setTextColor(Color.parseColor("#212121"))
181 | gravity = Gravity.CENTER
182 | textSize = 15f
183 | }
184 | tab.text = s
185 | tabLayout.addTab(tab)
186 | }
187 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/OnSeekBarChangeAdapter.java:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app;
2 |
3 | import android.widget.SeekBar;
4 |
5 | public abstract class OnSeekBarChangeAdapter implements SeekBar.OnSeekBarChangeListener {
6 |
7 | @Override
8 | public void onStartTrackingTouch(SeekBar seekBar) {
9 |
10 | }
11 |
12 | @Override
13 | public void onStopTrackingTouch(SeekBar seekBar) {
14 |
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/ShadowColorSettingFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.graphics.Color
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.SeekBar
9 | import androidx.fragment.app.Fragment
10 | import androidx.lifecycle.ViewModelProvider
11 |
12 | class ShadowColorSettingFragment: Fragment() {
13 |
14 | private val viewModel :DemoViewModel by lazy {
15 | ViewModelProvider(requireActivity())[DemoViewModel::class.java]
16 | }
17 |
18 | override fun onCreateView(
19 | inflater: LayoutInflater,
20 | container: ViewGroup?,
21 | savedInstanceState: Bundle?
22 | ): View? {
23 | return inflater.inflate(R.layout.fragment_shadow_color_setting, container, false)
24 | }
25 |
26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
27 | val sbAlpha = view.findViewById(R.id.sbAlpha)
28 | val sbR = view.findViewById(R.id.sbR)
29 | val sbG = view.findViewById(R.id.sbG)
30 | val sbB = view.findViewById(R.id.sbB)
31 | sbAlpha.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
32 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
33 | viewModel.shadowStartAlpha.value = progress
34 | }
35 | })
36 | val colorChange = object : OnSeekBarChangeAdapter() {
37 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
38 | viewModel.shadowColor.value = Color.argb(
39 | sbAlpha.progress,
40 | sbR.progress,
41 | sbG.progress,
42 | sbB.progress
43 | )
44 | }
45 | }
46 | sbR.setOnSeekBarChangeListener(colorChange)
47 | sbG.setOnSeekBarChangeListener(colorChange)
48 | sbB.setOnSeekBarChangeListener(colorChange)
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/ShadowOffsetSettingFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.SeekBar
8 | import androidx.fragment.app.Fragment
9 | import androidx.lifecycle.ViewModelProvider
10 |
11 | class ShadowOffsetSettingFragment: Fragment() {
12 |
13 | private var offset = 0
14 |
15 | private val viewModel :DemoViewModel by lazy {
16 | ViewModelProvider(requireActivity())[DemoViewModel::class.java]
17 | }
18 |
19 | override fun onCreateView(
20 | inflater: LayoutInflater,
21 | container: ViewGroup?,
22 | savedInstanceState: Bundle?
23 | ): View? {
24 | return inflater.inflate(R.layout.fragment_shadow_offset_setting, container, false)
25 | }
26 |
27 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
28 | val sbLO = view.findViewById(R.id.sbLO)
29 | val sbRO = view.findViewById(R.id.sbRO)
30 | val sbTO = view.findViewById(R.id.sbTO)
31 | val sbBO = view.findViewById(R.id.sbBO)
32 | val sbFourO = view.findViewById(R.id.sbFourO)
33 | viewModel.shadowSize.observe(viewLifecycleOwner){
34 | offset = 100 - it / 2
35 | }
36 | sbLO.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
37 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
38 | viewModel.leftOffset.value = progress.toFloat() - offset
39 | }
40 | })
41 | sbRO.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
42 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
43 | viewModel.rightOffset.value = progress.toFloat() - offset
44 | }
45 | })
46 | sbTO.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
47 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
48 | viewModel.topOffset.value = progress.toFloat() - offset
49 | }
50 | })
51 | sbBO.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
52 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
53 | viewModel.bottomOffset.value = progress.toFloat() - offset
54 | }
55 | })
56 | sbFourO.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
57 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
58 | viewModel.shadowOffsetCenter.value = progress.toFloat() - offset
59 | }
60 | })
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/ShadowShapeSettingFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.RadioGroup
8 | import androidx.fragment.app.Fragment
9 | import androidx.lifecycle.ViewModelProvider
10 | import www.linwg.org.lib.LCardView
11 |
12 | class ShadowShapeSettingFragment : Fragment() {
13 |
14 | private val viewModel: DemoViewModel by lazy {
15 | ViewModelProvider(requireActivity())[DemoViewModel::class.java]
16 | }
17 |
18 | override fun onCreateView(
19 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
20 | ): View? {
21 | return inflater.inflate(R.layout.fragment_shadow_shape_setting, container, false)
22 | }
23 |
24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
25 | val rgShape = view.findViewById(R.id.rgShape)
26 | viewModel.shadowFluidShape.observe(viewLifecycleOwner) {
27 | if (it == LCardView.ADSORPTION) {
28 | rgShape.check(R.id.rbOne)
29 | } else {
30 | rgShape.check(R.id.rbTwo)
31 | }
32 | }
33 | rgShape.setOnCheckedChangeListener { _, checkedId ->
34 | viewModel.shadowFluidShape.value =
35 | if (checkedId == R.id.rbOne) LCardView.ADSORPTION else LCardView.LINEAR
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/ShadowSizeSettingFragment.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.SeekBar
8 | import androidx.fragment.app.Fragment
9 | import androidx.lifecycle.ViewModelProvider
10 |
11 | class ShadowSizeSettingFragment : Fragment() {
12 |
13 | private val viewModel: DemoViewModel by lazy {
14 | ViewModelProvider(requireActivity())[DemoViewModel::class.java]
15 | }
16 |
17 | override fun onCreateView(
18 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
19 | ): View? {
20 | return inflater.inflate(R.layout.fragment_shadow_size_setting, container, false)
21 | }
22 |
23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
24 | val sbSS = view.findViewById(R.id.sbSS)
25 | val sbDLSS = view.findViewById(R.id.sbDLSS)
26 | val sbDTSS = view.findViewById(R.id.sbDTSS)
27 | val sbDRSS = view.findViewById(R.id.sbDRSS)
28 | val sbDBSS = view.findViewById(R.id.sbDBSS)
29 | sbSS.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
30 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
31 | viewModel.shadowSize.value = progress
32 | }
33 | })
34 | sbDLSS.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
35 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
36 | viewModel.leftWidthDecrement.value = progress.toFloat()
37 | }
38 | })
39 | sbDTSS.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
40 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
41 | viewModel.topWidthDecrement.value = progress.toFloat()
42 | }
43 | })
44 | sbDRSS.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
45 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
46 | viewModel.rightWidthDecrement.value = progress.toFloat()
47 | }
48 | })
49 | sbDBSS.setOnSeekBarChangeListener(object : OnSeekBarChangeAdapter() {
50 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
51 | viewModel.bottomWidthDecrement.value = progress.toFloat()
52 | }
53 | })
54 | }
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/www/linwg/org/app/TestActivity.java:
--------------------------------------------------------------------------------
1 | package www.linwg.org.app;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.MotionEvent;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.annotation.Nullable;
12 | import androidx.appcompat.app.AppCompatActivity;
13 | import androidx.recyclerview.widget.LinearLayoutManager;
14 | import androidx.recyclerview.widget.RecyclerView;
15 |
16 | import www.linwg.org.lib.LCardView;
17 |
18 | public class TestActivity extends AppCompatActivity {
19 |
20 |
21 | @Override
22 | protected void onCreate(@Nullable Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_test);
25 | RecyclerView recyclerView = findViewById(R.id.recyclerView);
26 | recyclerView.setLayoutManager(new LinearLayoutManager(this));
27 | recyclerView.setAdapter(new RecyclerView.Adapter() {
28 | @NonNull
29 | @Override
30 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
31 | View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_test3, parent, false);
32 | return new RecyclerView.ViewHolder(itemView) {
33 | };
34 | }
35 |
36 | @Override
37 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
38 | View tv = holder.itemView.findViewById(R.id.tv);
39 | View iv = holder.itemView.findViewById(R.id.iv);
40 | tv.setOnClickListener(new View.OnClickListener() {
41 | @Override
42 | public void onClick(View v) {
43 | iv.setVisibility(iv.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
44 | }
45 | });
46 | LCardView card = holder.itemView.findViewById(R.id.card);
47 | if (holder.getLayoutPosition() == 0) {
48 | float bottomShadowDecrement = card.getBottomShadowDecrement();
49 | float topShadowDecrement = card.getTopShadowDecrement();
50 | float leftShadowDecrement = card.getLeftShadowDecrement();
51 | float rightShadowDecrement = card.getRightShadowDecrement();
52 | float startDecrement = card.getShadowSize() * 0.8f;
53 | card.setOnTouchListener(new View.OnTouchListener() {
54 | @Override
55 | public boolean onTouch(View v, MotionEvent event) {
56 |
57 | if (event.getAction() == MotionEvent.ACTION_DOWN) {
58 | card.properties()
59 | .shadowAlpha(120)
60 | .leftShadowDecrement(startDecrement)
61 | .bottomShadowDecrement(startDecrement)
62 | .rightShadowDecrement(startDecrement)
63 | .topShadowDecrement(startDecrement);
64 | }
65 | if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) {
66 | card.properties()
67 | .shadowAlpha(80)
68 | .leftShadowDecrement(leftShadowDecrement)
69 | .bottomShadowDecrement(bottomShadowDecrement)
70 | .rightShadowDecrement(rightShadowDecrement)
71 | .topShadowDecrement(topShadowDecrement);
72 | }
73 | return true;
74 | }
75 | });
76 | card.setOnClickListener(null);
77 | } else {
78 | card.setOnTouchListener(null);
79 | card.setOnClickListener(new View.OnClickListener() {
80 | @Override
81 | public void onClick(View v) {
82 | float bottomShadowDecrement = card.getBottomShadowDecrement();
83 | float startDecrement = card.getShadowSize() * 0.5f;
84 | ValueAnimator animator = ValueAnimator.ofFloat(startDecrement, bottomShadowDecrement);
85 | animator.setDuration(200);
86 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
87 | @Override
88 | public void onAnimationUpdate(@NonNull ValueAnimator animation) {
89 | card.properties().bottomShadowDecrement((Float) animator.getAnimatedValue());
90 | }
91 | });
92 | animator.start();
93 | }
94 | });
95 | }
96 | }
97 |
98 | @Override
99 | public int getItemCount() {
100 | return 100;
101 | }
102 | });
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/indicator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
42 |
43 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test1.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
14 |
15 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_bottom_book_style_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
22 |
23 |
29 |
30 |
36 |
37 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_bottom_mesh_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
22 |
23 |
29 |
30 |
36 |
37 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_card_bg_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
15 |
16 |
21 |
22 |
27 |
28 |
29 |
35 |
36 |
42 |
43 |
51 |
52 |
53 |
56 |
57 |
63 |
64 |
69 |
70 |
71 |
72 |
78 |
79 |
84 |
85 |
90 |
91 |
96 |
97 |
102 |
103 |
108 |
109 |
110 |
115 |
116 |
122 |
123 |
130 |
131 |
137 |
138 |
144 |
145 |
146 |
152 |
153 |
159 |
160 |
167 |
168 |
174 |
175 |
176 |
182 |
183 |
189 |
190 |
196 |
197 |
204 |
205 |
206 |
207 |
208 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_card_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
19 |
20 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_corner_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
21 |
22 |
28 |
29 |
36 |
37 |
44 |
45 |
52 |
53 |
54 |
60 |
61 |
67 |
68 |
75 |
76 |
82 |
83 |
90 |
91 |
92 |
98 |
99 |
105 |
106 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_corner_sync.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
23 |
24 |
30 |
31 |
37 |
38 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_shadow_color_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
20 |
21 |
29 |
30 |
31 |
37 |
38 |
44 |
45 |
52 |
53 |
54 |
60 |
61 |
67 |
68 |
75 |
76 |
77 |
78 |
84 |
85 |
91 |
92 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_shadow_offset_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
21 |
22 |
28 |
29 |
37 |
38 |
45 |
46 |
54 |
55 |
56 |
62 |
63 |
69 |
70 |
78 |
79 |
85 |
86 |
94 |
95 |
96 |
102 |
103 |
109 |
110 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_shadow_shape_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
20 |
21 |
28 |
29 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_shadow_size_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
20 |
21 |
29 |
30 |
31 |
37 |
38 |
44 |
45 |
53 |
54 |
55 |
61 |
62 |
68 |
69 |
77 |
78 |
79 |
85 |
86 |
92 |
93 |
101 |
102 |
103 |
109 |
110 |
116 |
117 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
24 |
25 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_test2.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
17 |
18 |
23 |
24 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_test3.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
22 |
23 |
27 |
28 |
34 |
35 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/girl.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-hdpi/girl.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/refs.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | LCardView
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.7.10'
5 |
6 | repositories {
7 | maven {
8 | url 'https://maven.aliyun.com/repository/releases'
9 | }
10 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
11 | maven { url "https://maven.aliyun.com/repository/google" }
12 | maven { url "https://maven.aliyun.com/repository/jcenter" }
13 | gradlePluginPortal()
14 | google()
15 | mavenCentral()
16 | jcenter()
17 | }
18 | dependencies {
19 | classpath 'com.android.tools.build:gradle:7.2.2'
20 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
21 |
22 | // NOTE: Do not place your application dependencies here; they belong
23 | // in the individual module build.gradle files
24 | }
25 | }
26 |
27 | allprojects {
28 | repositories {
29 | maven {
30 | url 'https://maven.aliyun.com/repository/releases'
31 | }
32 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
33 | maven { url "https://maven.aliyun.com/repository/google" }
34 | maven { url "https://maven.aliyun.com/repository/jcenter" }
35 | google()
36 | maven { url "https://jitpack.io" }//必须添加这行
37 | maven {
38 | allowInsecureProtocol = true
39 | url "http://maven.aliyun.com/nexus/content/repositories/releases"
40 | }
41 | maven { url 'https://oss.sonatype.org/content/repositories/public/' }
42 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
43 | mavenCentral()
44 | jcenter()
45 | }
46 | tasks.withType(Javadoc) {
47 | options{
48 | encoding "UTF-8"
49 | charSet 'UTF-8'
50 | links "http://docs.oracle.com/javase/7/docs/api"
51 | }
52 | }
53 | }
54 |
55 | task clean(type: Delete) {
56 | delete rootProject.buildDir
57 | }
58 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | org.gradle.jvmargs=-Xmx1536m
12 | # When configured, Gradle will run in incubating parallel mode.
13 | # This option should only be used with decoupled projects. More details, visit
14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
15 | # org.gradle.parallel=true
16 |
17 | # signing information # ??ID??8? 3F4853570E4B754B5FFDDA712ECC233F04E06F1A
18 | signing.keyId=DE977BC7
19 | signing.password=lylove1314
20 | signing.secretKeyRingFile=../sering.gpg
21 |
22 | # sonatype account
23 | ossrhUsername=***
24 | ossrhPassword=***
25 |
26 | PROJ_GROUP=io.github.linwg1988
27 | PROJ_BASENAME=lcardview-kt
28 | PROJ_VERSION=1.0.4
29 | PROJ_WEBSITEURL=https://github.com/linwg1988/LCardView
30 | PROJ_VCSURL=https://github.com/linwg1988/LCardView.git
31 | PROJ_URL=https://github.com/linwg1988/LCardView/master
32 | PROJ_DESCRIPTION=A libary uses for browsing image detail.
33 |
34 | DEVELOPER_ID=linwg1988
35 | DEVELOPER_NAME=linwg1988
36 | DEVELOPER_EMAIL=273512894@qq.com
37 |
38 | sonartypeStaging=https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
39 | sonatypeSnapshots=https://s01.oss.sonatype.org/content/repositories/snapshots/
40 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue May 21 22:10:53 GMT+08:00 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/kotlin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/kotlin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'maven-publish'
4 | apply plugin: 'signing'
5 |
6 | android {
7 | compileSdk 33
8 |
9 | defaultConfig {
10 | minSdk 21
11 | targetSdk 33
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 | compileOptions {
18 | sourceCompatibility JavaVersion.VERSION_1_8
19 | targetCompatibility JavaVersion.VERSION_1_8
20 | }
21 | kotlinOptions {
22 | jvmTarget = '1.8'
23 | }
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 | }
31 |
32 | dependencies {
33 | implementation fileTree(dir: "libs", include: ["*.jar"])
34 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
35 | implementation 'androidx.appcompat:appcompat:1.5.0'
36 | }
37 |
38 | // add publish script
39 | publishing {
40 | publications {
41 | release(MavenPublication) {
42 | pom {
43 | name = 'LCardView'
44 | description = 'A libary uses for build shadow for android view.'
45 | url = 'https://github.com/linwg1988/LCardView'
46 |
47 | licenses {
48 | license {
49 | name = 'The Apache License, Version 2.0'
50 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
51 | }
52 | }
53 |
54 | developers {
55 | developer {
56 | id = 'linwg1988'
57 | name = 'linwg1988'
58 | email = '273512894@qq.com'
59 | }
60 | }
61 |
62 | scm {
63 | connection = 'https://github.com/linwg1988/LCardView.git'
64 | developerConnection = 'https://github.com/linwg1988/LCardView.git'
65 | url = 'https://github.com/linwg1988/LCardView'
66 | }
67 | }
68 |
69 | groupId "io.github.linwg1988"
70 | artifactId "lcardview-kt"
71 | version "1.0.4"
72 |
73 | afterEvaluate {
74 | from components.release
75 | }
76 | }
77 | }
78 | repositories {
79 | maven {
80 | url sonartypeStaging
81 | credentials {
82 | username ossrhUsername // ossrhUsername is your sonatype username
83 | password ossrhPassword // ossrhUsername is your sonatype password
84 | }
85 | }
86 | }
87 | }
88 |
89 | // signing, this need key, secret, we put it into gradle.properties
90 | signing {
91 | sign publishing.publications.release
92 | }
--------------------------------------------------------------------------------
/kotlin/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/kotlin/consumer-rules.pro
--------------------------------------------------------------------------------
/kotlin/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/kotlin/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/kotlin/src/main/java/www/linwg/org/lib/IShadow.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.lib
2 |
3 | import android.graphics.*
4 |
5 | interface IShadow {
6 |
7 | companion object {
8 | const val TOP = 0
9 | const val RIGHT = 1
10 | const val BOTTOM = 2
11 | const val LEFT = 3
12 | const val LEFT_TOP = 4
13 | const val RIGHT_TOP = 5
14 | const val RIGHT_BOTTOM = 6
15 | const val LEFT_BOTTOM = 7
16 | }
17 |
18 | fun markColorChange()
19 |
20 | fun onShapeModeChange(mode: Int)
21 |
22 | fun setMode(mode: Int)
23 |
24 | fun draw(canvas: Canvas, path: Path, paint: Paint)
25 |
26 | fun onAttachedToWindow()
27 |
28 | fun onDetachedFromWindow()
29 |
30 | fun onDestroy()
31 | }
32 |
33 | abstract class BaseShadow() : IShadow {
34 | val frame: RectF = RectF()
35 | var colorChange = false
36 | var alphaHalf = false
37 | var useShadowPool = false
38 | private var fadeColors: IntArray? = null
39 | private var hasBeenDetached = false
40 |
41 | override fun markColorChange() {
42 | //mark shader should recreate when onFrameChange call
43 | colorChange = true
44 | }
45 |
46 | fun markColorAlphaHalf() {
47 | colorChange = true
48 | alphaHalf = true
49 | }
50 |
51 | fun finishColorAlphaHalf(){
52 | colorChange = true
53 | alphaHalf = false
54 | }
55 |
56 | fun halfAlpha(colors: IntArray): IntArray {
57 | if (fadeColors == null) {
58 | fadeColors = IntArray(colors.size) {
59 | newColor(it, colors)
60 | }
61 | } else {
62 | for (i in colors.indices) {
63 | fadeColors!![i] = newColor(i, colors)
64 | }
65 | }
66 | return fadeColors!!
67 | }
68 |
69 | private fun newColor(index: Int, colors: IntArray): Int {
70 | val alpha = Color.alpha(colors[index]) / 2
71 | val r = Color.red(colors[index])
72 | val g = Color.green(colors[index])
73 | val b = Color.blue(colors[index])
74 | return Color.argb(alpha, r, g, b)
75 | }
76 |
77 | override fun onAttachedToWindow() {
78 | if (hasBeenDetached) {
79 | hasBeenDetached = false
80 | recreateShader()
81 | }
82 | }
83 |
84 | abstract fun recreateShader()
85 |
86 | override fun onDetachedFromWindow() {
87 | hasBeenDetached = true
88 | onDestroy()
89 | }
90 | }
--------------------------------------------------------------------------------
/kotlin/src/main/java/www/linwg/org/lib/LinearShadow.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.lib
2 |
3 | import android.graphics.*
4 | import kotlin.math.pow
5 | import kotlin.math.roundToInt
6 |
7 | class LinearShadow(private val colors: IntArray, percent: Float, private val part: Int) : BaseShadow() {
8 | private var curvatureChange = false
9 | private var bookRadiusChange = false
10 | private var meshTypeChange = false
11 | var curvature: Float = 4f
12 | set(value) {
13 | curvatureChange = field != value
14 | field = value
15 | }
16 | private val percents: FloatArray = floatArrayOf(0f, percent, (1 - percent) / 2 + percent, 1f)
17 | private var linearShape: LinearGradient? = null
18 | private var adsorptionShape: LinearGradient? = null
19 | private var shader: LinearGradient? = null
20 | private val origin: RectF = RectF()
21 | private var mode: Int = LCardView.ADSORPTION
22 | private val matrix = Matrix()
23 | private var meshBitmap: Bitmap? = null
24 | var linearBookEffect: Boolean = false
25 | set(value) {
26 | field = value
27 | if (value) {
28 | if (curveShadowEffect) {
29 | meshTypeChange = true
30 | }
31 | curveShadowEffect = false
32 | }
33 | }
34 | var curveShadowEffect: Boolean = false
35 | set(value) {
36 | field = value
37 | if (value) {
38 | if (linearBookEffect) {
39 | meshTypeChange = true
40 | }
41 | linearBookEffect = false
42 | }
43 | }
44 | var bookRadius: Float = 0f
45 | set(value) {
46 | bookRadiusChange = field != value
47 | field = value
48 | }
49 | private val mPaint = Paint()
50 | private var decrementChange = false
51 | private var widthDecrement = 0f
52 | private var heightDecrement = 0f
53 |
54 | fun setDecrement(x: Float, y: Float) {
55 | if (part == IShadow.LEFT || part == IShadow.RIGHT) {
56 | if (widthDecrement != x) {
57 | widthDecrement = x
58 | decrementChange = true
59 | }
60 | }
61 | if (part == IShadow.TOP || part == IShadow.BOTTOM) {
62 | if (heightDecrement != y) {
63 | heightDecrement = y
64 | decrementChange = true
65 | }
66 | }
67 | }
68 |
69 | private fun createShader(assign: Boolean = true): LinearGradient {
70 | val colors = if (alphaHalf) {
71 | halfAlpha(colors)
72 | } else {
73 | this.colors
74 | }
75 | val shader = if (!useShadowPool) {
76 | newShader(colors)
77 | } else {
78 | val key = ShadowPool.getLinearKey(
79 | origin.width().toInt(),
80 | origin.height().toInt(),
81 | widthDecrement,
82 | heightDecrement,
83 | mode,
84 | part,
85 | colors[0]
86 | )
87 | val linearGradient = ShadowPool.get(key) as LinearGradient? ?: newShader(colors)
88 | ShadowPool.put(key, linearGradient)
89 | linearGradient
90 | }
91 |
92 | if (curveShadowEffect || linearBookEffect) {
93 | meshBitmap = if (!useShadowPool) {
94 | newMesh(shader)
95 | } else {
96 | var mesh = ShadowPool.getMesh(
97 | frame.width().toInt(),
98 | frame.height().toInt(),
99 | (curvature * 1000).toInt(),
100 | bookRadius,
101 | linearBookEffect,
102 | colors[0]
103 | )
104 | if (mesh == null) {
105 | mesh = newMesh(shader)
106 | }
107 | ShadowPool.putMesh(
108 | frame.width().toInt(),
109 | frame.height().toInt(),
110 | (curvature * 1000).toInt(),
111 | colors[0],
112 | bookRadius,
113 | linearBookEffect,
114 | mesh
115 | )
116 | mesh
117 | }
118 | }
119 |
120 | if (assign) {
121 | if (mode == LCardView.ADSORPTION) {
122 | adsorptionShape = shader
123 | linearShape = null
124 | } else {
125 | linearShape = shader
126 | adsorptionShape = null
127 | }
128 | }
129 | return shader
130 | }
131 |
132 | private fun newMesh(shader: Shader): Bitmap {
133 | val bitmap = ShadowPool.getDirty(frame.width().toInt(), frame.height().toInt())
134 | val vertWidthSize = (frame.width() / 5).roundToInt()
135 | val vertHeightSize = (frame.height() / 5).roundToInt()
136 | val verts = initVerts(vertWidthSize, vertHeightSize)
137 | val c = Canvas(bitmap)
138 | matrix.setTranslate(-frame.left, -frame.top)
139 | shader.setLocalMatrix(matrix)
140 | mPaint.shader = shader
141 | c.drawRect(Rect(0, 0, bitmap.width, bitmap.height), mPaint)
142 |
143 | val dest = ShadowPool.getDirty(frame.width().toInt(), frame.height().toInt(), true)
144 | val meshCanvas = Canvas(dest)
145 | meshCanvas.drawBitmapMesh(bitmap, vertWidthSize, vertHeightSize, verts, 0, null, 0, null)
146 | ShadowPool.putDirty(bitmap)
147 | return dest
148 | }
149 |
150 | private fun newShader(colors: IntArray): LinearGradient {
151 | return when (part) {
152 | IShadow.TOP -> LinearGradient(
153 | origin.left,
154 | origin.bottom,
155 | origin.left,
156 | origin.top + heightDecrement,
157 | colors,
158 | percents,
159 | Shader.TileMode.CLAMP
160 | )
161 | IShadow.RIGHT -> LinearGradient(
162 | origin.left,
163 | origin.top,
164 | origin.right - widthDecrement,
165 | origin.top,
166 | colors,
167 | percents,
168 | Shader.TileMode.CLAMP
169 | )
170 | IShadow.BOTTOM -> LinearGradient(
171 | origin.left,
172 | origin.top,
173 | origin.left,
174 | origin.bottom - heightDecrement,
175 | colors,
176 | percents,
177 | Shader.TileMode.CLAMP
178 | )
179 | else -> LinearGradient(
180 | origin.right,
181 | origin.top,
182 | origin.left + widthDecrement,
183 | origin.top,
184 | colors,
185 | percents,
186 | Shader.TileMode.CLAMP
187 | )
188 | }
189 | }
190 |
191 | fun onFrameChange() {
192 | val needRecreate: Boolean
193 | if (origin.isEmpty || shader == null || meshTypeChange || colorChange || (part == IShadow.BOTTOM && ((curveShadowEffect && (curvatureChange || meshBitmap == null)) || (linearBookEffect && (bookRadiusChange || meshBitmap == null)))) || decrementChange) {
194 | colorChange = false
195 | curvatureChange = false
196 | decrementChange = false
197 | bookRadiusChange = false
198 | meshTypeChange = false
199 | needRecreate = true
200 | } else {
201 | needRecreate = when (part) {
202 | IShadow.TOP -> origin.height() != frame.height()
203 | IShadow.RIGHT -> origin.width() != frame.width()
204 | IShadow.BOTTOM -> origin.height() != frame.height() || ((curveShadowEffect || linearBookEffect) && origin.width() != frame.width())
205 | IShadow.LEFT -> origin.width() != frame.width()
206 | else -> false
207 | }
208 | }
209 | if (needRecreate) {
210 | colorChange = false
211 | origin.set(frame)
212 | shader = createShader()
213 | return
214 | }
215 | matrix.setTranslate(frame.left - origin.left, frame.top - origin.top)
216 | shader!!.setLocalMatrix(matrix)
217 | }
218 |
219 | private fun initVerts(vertWidthSize: Int, vertHeightSize: Int): FloatArray {
220 |
221 | val count = (vertWidthSize + 1) * (vertHeightSize + 1)
222 | val verts = FloatArray(count * 2)
223 |
224 | val w = frame.width()
225 | val h = frame.height()
226 | var index = 0
227 | //将图片分割,然后保存坐标点
228 |
229 | val b = h - h / curvature
230 | val k = b / (w / 2).pow(2)
231 |
232 | for (y in 0..vertHeightSize) {
233 | for (x in 0..vertWidthSize) {
234 | val fx = (w / vertWidthSize) * x
235 | val oy = (h / vertHeightSize) * y
236 | val fy = if (linearBookEffect) {
237 | val lk = h * bookRadius / (w / 2)
238 | val funcY = if (fx < w / 2) {
239 | fx * lk
240 | } else {
241 | (h * bookRadius) - ((fx - w / 2) * lk)
242 | }
243 | oy - funcY
244 | } else {
245 | val funcY = h - getFY(fx - w / 2, k, b)
246 | oy * (funcY / h)
247 | }
248 |
249 | verts[index * 2 + 0] = fx
250 | verts[index * 2 + 1] = fy
251 | index++
252 | }
253 | }
254 | return verts
255 | }
256 |
257 |
258 | private fun getFY(x: Float, k: Float, b: Float): Float {
259 | return -k * x.pow(2) + b
260 | }
261 |
262 | override fun onShapeModeChange(mode: Int) {
263 | this.mode = mode
264 | shader = if (mode == LCardView.ADSORPTION) {
265 | if (adsorptionShape == null) {
266 | adsorptionShape = createShader(false)
267 | }
268 | adsorptionShape
269 | } else {
270 | if (linearShape == null) {
271 | linearShape = createShader(false)
272 | }
273 | linearShape
274 | }
275 | }
276 |
277 | override fun setMode(mode: Int) {
278 | this.mode = mode
279 | }
280 |
281 | override fun draw(canvas: Canvas, path: Path, paint: Paint) {
282 | if (frame.width() <= 0 || frame.height() <= 0) return
283 | if(frame.width() - widthDecrement <= 0f || frame.height() - heightDecrement <= 0f) return
284 | paint.shader = shader
285 | if (part == IShadow.BOTTOM && (linearBookEffect || curveShadowEffect)) {
286 | canvas.save()
287 | canvas.clipRect(frame)
288 | canvas.translate(frame.left, frame.top)
289 | canvas.drawBitmap(meshBitmap!!, 0f, 0f, null)
290 | canvas.restore()
291 | } else {
292 | canvas.drawRect(frame.left, frame.top, frame.right, frame.bottom, paint)
293 | }
294 | }
295 |
296 | override fun recreateShader() {
297 | shader = createShader(true)
298 | }
299 |
300 | override fun onDestroy() {
301 | linearShape = null
302 | adsorptionShape = null
303 | shader = null
304 | meshBitmap = null
305 | }
306 | }
--------------------------------------------------------------------------------
/kotlin/src/main/java/www/linwg/org/lib/RadialShadow.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.lib
2 |
3 | import android.graphics.*
4 | import kotlin.math.roundToInt
5 | import kotlin.math.sqrt
6 |
7 | class RadialShadow(private val colors: IntArray, private val part: Int) : BaseShadow() {
8 | private val percent = 0.33f
9 | private val percents = floatArrayOf(0f, 0.33f, 0.66f, 1f)
10 | private var linearShape: RadialGradient? = null
11 | private var adsorptionShape: RadialGradient? = null
12 | private var shader: RadialGradient? = null
13 | private val center = PointF()
14 | private var shaderCornerRadius: Float = 0f
15 | private val matrix = Matrix()
16 | var cornerRadius: Float = 0f
17 | private var mode: Int = LCardView.ADSORPTION
18 | val mPath = Path()
19 | private var decrementChange = false
20 | private var widthDecrement = 0f
21 | private var heightDecrement = 0f
22 | private var meshBitmap: Bitmap? = null
23 | private val paint: Paint by lazy { Paint() }
24 |
25 | fun setDecrement(x: Float, y: Float) {
26 | if (widthDecrement != x) {
27 | widthDecrement = x
28 | decrementChange = true
29 | }
30 | if (heightDecrement != y) {
31 | heightDecrement = y
32 | decrementChange = true
33 | }
34 | }
35 |
36 | private fun createShader(
37 | centerX: Float = center.x,
38 | centerY: Float = center.y,
39 | percentChange: Boolean = true,
40 | assign: Boolean = true
41 | ): RadialGradient {
42 | val colors = if (alphaHalf) {
43 | halfAlpha(colors)
44 | } else {
45 | this.colors
46 | }
47 | if (percentChange) {
48 | percents[0] = cornerRadius / shaderCornerRadius
49 | percents[1] = (1 - percents[0]) * percent + percents[0]
50 | percents[2] = (1 - percents[1]) / 2 + percents[1]
51 | }
52 | val shader = if (!useShadowPool) {
53 | newShader(centerX, centerY, colors)
54 | } else {
55 | val key = ShadowPool.getRadialKey(
56 | frame.width().toInt(), frame.height().toInt(), mode, part, cornerRadius, colors[0]
57 | )
58 | val radialGradient =
59 | ShadowPool.get(key) as RadialGradient? ?: newShader(centerX, centerY, colors)
60 | ShadowPool.put(key, radialGradient)
61 | radialGradient
62 | }
63 |
64 | if (assign) {
65 | if (mode == LCardView.ADSORPTION) {
66 | adsorptionShape = shader
67 | linearShape = null
68 | } else {
69 | linearShape = shader
70 | adsorptionShape = null
71 | }
72 | }
73 | return shader
74 | }
75 |
76 | private fun makeMeshRadialGradient() {
77 | meshBitmap = if (!useShadowPool) {
78 | createMeshBitmap()
79 | } else {
80 | var mesh = ShadowPool.getMeshRadial(
81 | frame.width().toInt(),
82 | frame.height().toInt(),
83 | widthDecrement,
84 | heightDecrement,
85 | part,
86 | colors[0]
87 | )
88 | if (mesh == null) {
89 | mesh = createMeshBitmap()
90 | }
91 | ShadowPool.putMeshRadial(
92 | frame.width().toInt(),
93 | frame.height().toInt(),
94 | widthDecrement,
95 | heightDecrement,
96 | part,
97 | colors[0],
98 | mesh
99 | )
100 | mesh
101 | }
102 | }
103 |
104 | private fun createMeshBitmap(): Bitmap {
105 | val vertWidthSize = (frame.width() / 10).roundToInt()
106 | val vertHeightSize = (frame.height() / 10).roundToInt()
107 | val count = (vertWidthSize + 1) * (vertHeightSize + 1)
108 | val verts = FloatArray(count * 2)
109 | val w = frame.width() / 2
110 | val h = frame.height() / 2
111 | var index = 0
112 | val scaleY = (shaderCornerRadius - heightDecrement) / shaderCornerRadius
113 | val scaleX = (shaderCornerRadius - widthDecrement) / shaderCornerRadius
114 | for (y in 0..vertHeightSize) {
115 | for (x in 0..vertWidthSize) {
116 | val originX: Float
117 | val originY: Float
118 | when (part) {
119 | IShadow.LEFT_TOP -> {
120 | originX = w - (w / vertWidthSize) * x
121 | originY = h - (h / vertHeightSize) * y
122 | }
123 | IShadow.RIGHT_TOP -> {
124 | originX = (w / vertWidthSize) * x
125 | originY = h - (h / vertHeightSize) * y
126 | }
127 | IShadow.RIGHT_BOTTOM -> {
128 | originX = (w / vertWidthSize) * x
129 | originY = (h / vertHeightSize) * y
130 | }
131 | else -> {
132 | originX = w - (w / vertWidthSize) * x
133 | originY = (h / vertHeightSize) * y
134 | }
135 | }
136 | val pointOnCircle = getPointOnCircle(originX, originY, shaderCornerRadius)
137 | val pointOnEllipse = getPointOnEllipse(originX, originY, w * scaleX, h * scaleY)
138 |
139 | val lengthFromCircleToCenter = getLengthFromCenter(pointOnCircle)
140 | val lengthFromEllipseToCenter = getLengthFromCenter(pointOnEllipse)
141 | val lengthFromCurrentToCenter = getLengthFromCenter(Pair(originX, originY))
142 |
143 | if (lengthFromCurrentToCenter > cornerRadius) {
144 | val originLengthToStaticCircle = lengthFromCurrentToCenter - cornerRadius
145 | val scale =
146 | (lengthFromEllipseToCenter - cornerRadius) / (lengthFromCircleToCenter - cornerRadius)
147 | val targetLength = originLengthToStaticCircle * scale + cornerRadius
148 | val point = lineSegmentsConvertedToCoordinate(originX, originY, targetLength)
149 |
150 | when (part) {
151 | IShadow.LEFT_TOP -> {
152 | verts[index * 2 + 0] = w - point.first
153 | verts[index * 2 + 1] = h - point.second
154 | }
155 | IShadow.RIGHT_TOP -> {
156 | verts[index * 2 + 0] = point.first
157 | verts[index * 2 + 1] = h - point.second
158 | }
159 | IShadow.RIGHT_BOTTOM -> {
160 | verts[index * 2 + 0] = point.first
161 | verts[index * 2 + 1] = point.second
162 | }
163 | else -> {
164 | verts[index * 2 + 0] = w - point.first
165 | verts[index * 2 + 1] = point.second
166 | }
167 | }
168 | } else {
169 | when (part) {
170 | IShadow.LEFT_TOP -> {
171 | verts[index * 2 + 0] = w - originX
172 | verts[index * 2 + 1] = h - originY
173 | }
174 | IShadow.RIGHT_TOP -> {
175 | verts[index * 2 + 0] = originX
176 | verts[index * 2 + 1] = h - originY
177 | }
178 | IShadow.RIGHT_BOTTOM -> {
179 | verts[index * 2 + 0] = originX
180 | verts[index * 2 + 1] = originY
181 | }
182 | else -> {
183 | verts[index * 2 + 0] = w - originX
184 | verts[index * 2 + 1] = originY
185 | }
186 | }
187 |
188 | }
189 | index++
190 | }
191 | }
192 |
193 | val bitmap = ShadowPool.getDirty((frame.width() / 2).toInt(), (frame.height() / 2).toInt())
194 | val c = Canvas(bitmap)
195 |
196 | if (part == IShadow.RIGHT_TOP) {
197 | matrix.setTranslate(-frame.centerX(), 0f)
198 | shader?.setLocalMatrix(matrix)
199 | }
200 |
201 | if (part == IShadow.RIGHT_BOTTOM) {
202 | matrix.setTranslate(-frame.centerX(), -frame.centerY())
203 | shader?.setLocalMatrix(matrix)
204 | }
205 |
206 | if (part == IShadow.LEFT_BOTTOM) {
207 | matrix.setTranslate(0f, -frame.centerY())
208 | shader?.setLocalMatrix(matrix)
209 | }
210 |
211 | paint.shader = shader
212 | c.drawRect(0f, 0f, frame.centerX(), frame.centerY(), paint)
213 | val dest = ShadowPool.getDirty(
214 | (frame.width() / 2).toInt(), (frame.height() / 2).toInt(), true
215 | )
216 | val tempCanvas = Canvas(dest)
217 | tempCanvas.drawBitmapMesh(bitmap, vertWidthSize, vertHeightSize, verts, 0, null, 0, null)
218 | ShadowPool.putDirty(bitmap)
219 | return dest
220 | }
221 |
222 | private fun getLengthFromCenter(p: Pair): Float {
223 | return sqrt(p.first * p.first + p.second * p.second)
224 | }
225 |
226 | /**
227 | * 线段转换成坐标
228 | */
229 | private fun lineSegmentsConvertedToCoordinate(
230 | x: Float, y: Float, length: Float
231 | ): Pair {
232 | if (x == 0.0f) {
233 | return Pair(0f, length)
234 | }
235 | val k = y / x
236 | val resultX = length / sqrt(1 + k * k)
237 | val resultY = resultX * k
238 | return Pair(resultX, resultY)
239 | }
240 |
241 | /**
242 | * 获取过点与圆心的直线与圆的交点
243 | *
244 | */
245 | private fun getPointOnCircle(x: Float, y: Float, r: Float): Pair {
246 | if (x == 0.0f) {
247 | return Pair(0.0f, r)
248 | }
249 | val k = y / x
250 | val resultX = r / sqrt((1 + k * k))
251 | val resultY = resultX * k
252 | return Pair(resultX, resultY)
253 | }
254 |
255 | /**
256 | * 获取过点与椭圆圆心的直线与圆的交点
257 | *
258 | */
259 | private fun getPointOnEllipse(x: Float, y: Float, a: Float, b: Float): Pair {
260 | if (x == 0.0f) {
261 | return Pair(0.0f, b)
262 | }
263 | val k = y / x
264 | val resultX = a * b / sqrt((b * b + k * k * a * a))
265 | val resultY = resultX * k
266 | return Pair(resultX, resultY)
267 | }
268 |
269 | private fun newShader(centerX: Float, centerY: Float, colors: IntArray): RadialGradient {
270 | return RadialGradient(
271 | centerX, centerY, shaderCornerRadius, colors, percents, Shader.TileMode.CLAMP
272 | )
273 | }
274 |
275 | fun onFrameChange(centerX: Float, centerY: Float, r: Float) {
276 | if (shaderCornerRadius != r || shader == null || colorChange || decrementChange) {
277 | colorChange = false
278 | decrementChange = false
279 | //means we need create new shader
280 | shaderCornerRadius = r
281 | center.x = centerX
282 | center.y = centerY
283 | shader = createShader()
284 |
285 | if ((widthDecrement > 0f || heightDecrement > 0f)) {
286 | makeMeshRadialGradient()
287 | } else {
288 | if (meshBitmap != null) {
289 | ShadowPool.putDirty(meshBitmap!!, true)
290 | meshBitmap = null
291 | }
292 | }
293 |
294 | makePath()
295 | return
296 | }
297 | //check center point change
298 | matrix.setTranslate(centerX - center.x, centerY - center.y)
299 | shader!!.setLocalMatrix(matrix)
300 | makePath()
301 | }
302 |
303 | fun reset() {
304 | offsetLeft = 0f
305 | offsetTop = 0f
306 | offsetRight = 0f
307 | offsetBottom = 0f
308 | }
309 |
310 | fun makePath() {
311 | mPath.reset()
312 | when (part) {
313 | IShadow.LEFT_TOP -> {
314 | if (offsetRight > 0 || offsetBottom > 0) {
315 | val scaleX = (frame.width() - offsetRight) / frame.width()
316 | val scaleY = (frame.height() - offsetBottom) / frame.height()
317 | matrix.postScale(scaleX, scaleY, offsetRight, offsetBottom)
318 | shader!!.setLocalMatrix(matrix)
319 | }
320 |
321 | mPath.moveTo(frame.left, frame.centerY() - offsetBottom)
322 | mPath.lineTo(frame.centerX() - offsetRight, frame.centerY() - offsetBottom)
323 | mPath.lineTo(frame.centerX() - offsetRight, frame.top)
324 | mPath.lineTo(frame.left, frame.top)
325 | mPath.lineTo(frame.left, frame.centerY() - offsetBottom)
326 | mPath.close()
327 | }
328 | IShadow.RIGHT_TOP -> {
329 | mPath.moveTo(frame.centerX() + offsetLeft, frame.top)
330 | mPath.lineTo(frame.centerX() + offsetLeft, frame.centerY() - offsetBottom)
331 | mPath.lineTo(frame.right, frame.centerY() - offsetBottom)
332 | mPath.lineTo(frame.right, frame.top)
333 | mPath.lineTo(frame.centerX() + offsetLeft, frame.top)
334 | mPath.close()
335 | }
336 | IShadow.RIGHT_BOTTOM -> {
337 | mPath.moveTo(frame.right, frame.centerY() + offsetTop)
338 | mPath.lineTo(frame.centerX() + offsetLeft, frame.centerY() + offsetTop)
339 | mPath.lineTo(frame.centerX() + offsetLeft, frame.bottom)
340 | mPath.lineTo(frame.right, frame.bottom)
341 | mPath.lineTo(frame.right, frame.centerY() + offsetTop)
342 | mPath.close()
343 | }
344 | IShadow.LEFT_BOTTOM -> {
345 | mPath.moveTo(frame.centerX() - offsetRight, frame.bottom)
346 | mPath.lineTo(frame.centerX() - offsetRight, frame.centerY() + offsetTop)
347 | mPath.lineTo(frame.left, frame.centerY() + offsetTop)
348 | mPath.lineTo(frame.left, frame.bottom)
349 | mPath.lineTo(frame.centerX() - offsetRight, frame.bottom)
350 | mPath.close()
351 | }
352 | }
353 | }
354 |
355 | override fun onShapeModeChange(mode: Int) {
356 | this.mode = mode
357 | shader = if (mode == LCardView.ADSORPTION) {
358 | if (adsorptionShape == null) {
359 | adsorptionShape = createShader(percentChange = false, assign = false)
360 | }
361 | adsorptionShape
362 | } else {
363 | if (linearShape == null) {
364 | linearShape = createShader(percentChange = false, assign = false)
365 | }
366 | linearShape
367 | }
368 |
369 | if ((widthDecrement > 0f || heightDecrement > 0f)) {
370 | makeMeshRadialGradient()
371 | } else {
372 | if (meshBitmap != null) {
373 | ShadowPool.putDirty(meshBitmap!!, true)
374 | meshBitmap = null
375 | }
376 | }
377 | }
378 |
379 | override fun setMode(mode: Int) {
380 | this.mode = mode
381 | }
382 |
383 | override fun draw(canvas: Canvas, path: Path, paint: Paint) {
384 | if (frame.isEmpty) {
385 | return
386 | }
387 |
388 | if (meshBitmap != null) {
389 | val shadowSize = shaderCornerRadius - cornerRadius
390 | if(widthDecrement >= shadowSize && heightDecrement >= shadowSize) return
391 | when (part) {
392 | IShadow.RIGHT_TOP -> {
393 | canvas.save()
394 | canvas.translate(frame.centerX(), 0f)
395 | canvas.drawBitmap(meshBitmap!!, 0f, 0f, null)
396 | canvas.restore()
397 | }
398 | IShadow.RIGHT_BOTTOM -> {
399 | canvas.save()
400 | canvas.translate(frame.centerX(), frame.centerY())
401 | canvas.drawBitmap(meshBitmap!!, 0f, 0f, null)
402 | canvas.restore()
403 | }
404 | IShadow.LEFT_BOTTOM -> {
405 | canvas.save()
406 | canvas.translate(0f, frame.centerY())
407 | canvas.drawBitmap(meshBitmap!!, 0f, 0f, null)
408 | canvas.restore()
409 | }
410 | else -> {
411 | canvas.drawBitmap(meshBitmap!!, 0f, 0f, null)
412 | }
413 | }
414 | } else {
415 | paint.shader = shader
416 | canvas.drawPath(mPath, paint)
417 | }
418 | }
419 |
420 | override fun recreateShader() {
421 | shader = createShader(percentChange = false, assign = true)
422 |
423 |
424 | if ((widthDecrement > 0f || heightDecrement > 0f)) {
425 | makeMeshRadialGradient()
426 | } else {
427 | if (meshBitmap != null) {
428 | ShadowPool.putDirty(meshBitmap!!, true)
429 | meshBitmap = null
430 | }
431 | }
432 | }
433 |
434 | override fun onDestroy() {
435 | linearShape = null
436 | adsorptionShape = null
437 | meshBitmap = null
438 | shader = null
439 | }
440 |
441 | var offsetRight = 0f
442 | var offsetLeft = 0f
443 | var offsetTop = 0f
444 | var offsetBottom = 0f
445 | }
--------------------------------------------------------------------------------
/kotlin/src/main/java/www/linwg/org/lib/ShadowManager.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.lib
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.content.Context
6 | import android.graphics.Canvas
7 | import android.graphics.Paint
8 | import android.graphics.Path
9 | import android.graphics.RectF
10 | import android.os.Bundle
11 | import android.util.Log
12 | import androidx.lifecycle.Lifecycle
13 | import androidx.lifecycle.LifecycleEventObserver
14 | import androidx.lifecycle.LifecycleOwner
15 |
16 | internal class ShadowManager(colors: IntArray, percent: Float = 0.33f) {
17 | companion object {
18 | var hasReadLifeCircleSupport = false
19 | var lifeCirCleSupport = false
20 | }
21 |
22 | init {
23 | if (!hasReadLifeCircleSupport) {
24 | hasReadLifeCircleSupport = true
25 | try {
26 | Class.forName("androidx.lifecycle.Lifecycle")
27 | Log.i("LCardView", "Lifecycle support")
28 | lifeCirCleSupport = true
29 | } catch (e: ClassNotFoundException) {
30 | lifeCirCleSupport = false
31 | Log.w("LCardView", "Lifecycle not support")
32 | }
33 | }
34 | }
35 |
36 | /**
37 | * Corner shadows will be draw on 4 corner.
38 | */
39 | private val ltShader: RadialShadow = RadialShadow(colors, IShadow.LEFT_TOP)
40 | private val lbShader: RadialShadow = RadialShadow(colors, IShadow.LEFT_BOTTOM)
41 | private val rbShader: RadialShadow = RadialShadow(colors, IShadow.RIGHT_BOTTOM)
42 | private val rtShader: RadialShadow = RadialShadow(colors, IShadow.RIGHT_TOP)
43 |
44 | /**
45 | * Edge shadows will be draw on 4 edge.
46 | */
47 | private val tShadow: LinearShadow = LinearShadow(colors, percent, IShadow.TOP)
48 | private val rShadow: LinearShadow = LinearShadow(colors, percent, IShadow.RIGHT)
49 | private val bShadow: LinearShadow = LinearShadow(colors, percent, IShadow.BOTTOM)
50 | private val lShadow: LinearShadow = LinearShadow(colors, percent, IShadow.LEFT)
51 | val shaderArray =
52 | arrayOf(ltShader, tShadow, rtShader, rShadow, rbShader, bShadow, lbShader, lShadow)
53 | var useShadowPool: Boolean = false
54 | set(value) {
55 | field = value
56 | shaderArray.forEach {
57 | it.useShadowPool = value
58 | }
59 | }
60 | var linearBookEffect: Boolean = false
61 | set(value) {
62 | field = value
63 | bShadow.linearBookEffect = value
64 | }
65 | var curveShadowEffect: Boolean = false
66 | set(value) {
67 | field = value
68 | bShadow.curveShadowEffect = value
69 | }
70 |
71 | var fluidShape: Int = LCardView.ADSORPTION
72 | set(value) {
73 | field = value
74 | shaderArray.forEach {
75 | it.setMode(value)
76 | }
77 | }
78 |
79 | fun measureShadowPath(path: Path) {
80 | path.reset()
81 | path.moveTo(lShadow.frame.right, lShadow.frame.top)
82 | path.arcTo(
83 | RectF(
84 | lShadow.frame.right,
85 | tShadow.frame.bottom,
86 | lShadow.frame.right + ltShader.cornerRadius * 2,
87 | tShadow.frame.bottom + ltShader.cornerRadius * 2
88 | ), 180f, 90f
89 | )
90 | path.lineTo(tShadow.frame.right, tShadow.frame.bottom)
91 | path.arcTo(
92 | RectF(
93 | rShadow.frame.left - rtShader.cornerRadius * 2,
94 | rShadow.frame.top - rtShader.cornerRadius,
95 | rShadow.frame.left,
96 | rShadow.frame.top + rtShader.cornerRadius
97 | ), 270f, 90f
98 | )
99 | path.lineTo(rShadow.frame.left, rShadow.frame.bottom)
100 | path.arcTo(
101 | RectF(
102 | rShadow.frame.left - rbShader.cornerRadius * 2,
103 | rShadow.frame.bottom - rbShader.cornerRadius,
104 | rShadow.frame.left,
105 | rShadow.frame.bottom + rbShader.cornerRadius
106 | ), 0f, 90f
107 | )
108 | path.lineTo(bShadow.frame.left, bShadow.frame.top)
109 | path.arcTo(
110 | RectF(
111 | lShadow.frame.right,
112 | lShadow.frame.bottom - lbShader.cornerRadius,
113 | lShadow.frame.right + lbShader.cornerRadius * 2,
114 | lShadow.frame.bottom + lbShader.cornerRadius
115 | ), 90f, 90f
116 | )
117 | path.close()
118 | }
119 |
120 | fun needDrawOffsetShadowColor(cardView: LCardView): Boolean {
121 | return lShadow.frame.right < cardView.paddingLeft || tShadow.frame.bottom < cardView.paddingTop
122 | || rShadow.frame.left > cardView.viewWidth - cardView.paddingRight
123 | || bShadow.frame.top > cardView.viewHeight - cardView.paddingBottom
124 | }
125 |
126 | fun measureContentPath(
127 | cardView: LCardView,
128 | paperSyncCorner: Boolean,
129 | paperCorner: Float,
130 | path: Path,
131 | strokePath: Path,
132 | strokeWidth: Int
133 | ) {
134 | val startX = cardView.paddingLeft.toFloat()
135 | val startY = cardView.paddingTop.toFloat()
136 | val stopX = cardView.paddingRight.toFloat()
137 | val stopY = cardView.paddingBottom.toFloat()
138 | val viewHeight = cardView.viewHeight
139 | val viewWidth = cardView.viewWidth
140 | path.reset()
141 | strokePath.reset()
142 | if (paperSyncCorner) {
143 | path.moveTo(startX, startY + ltShader.cornerRadius)
144 | path.arcTo(
145 | RectF(
146 | startX,
147 | startY,
148 | ltShader.cornerRadius * 2 + startX,
149 | startY + ltShader.cornerRadius * 2
150 | ), 180f, 90f
151 | )
152 | path.lineTo(viewWidth - stopX - rtShader.cornerRadius, startY)
153 | path.arcTo(
154 | RectF(
155 | viewWidth - stopX - rtShader.cornerRadius * 2,
156 | startY,
157 | viewWidth - stopX,
158 | startY + rtShader.cornerRadius * 2
159 | ), 270f, 90f
160 | )
161 | path.lineTo(viewWidth - stopX, viewHeight - stopY - rbShader.cornerRadius)
162 | path.arcTo(
163 | RectF(
164 | viewWidth - stopX - rbShader.cornerRadius * 2,
165 | viewHeight - stopY - rbShader.cornerRadius * 2,
166 | viewWidth - stopX,
167 | viewHeight - stopY
168 | ), 0f, 90f
169 | )
170 | path.lineTo(startX + lbShader.cornerRadius, viewHeight - stopY)
171 | path.arcTo(
172 | RectF(
173 | startX,
174 | viewHeight - stopY - lbShader.cornerRadius * 2,
175 | startX + lbShader.cornerRadius * 2,
176 | viewHeight - stopY
177 | ), 90f, 90f
178 | )
179 |
180 | val offset = strokeWidth / 2f
181 | if (strokeWidth > 0) {
182 | strokePath.moveTo(startX + offset, startY + ltShader.cornerRadius)
183 | strokePath.arcTo(
184 | RectF(
185 | startX + offset,
186 | startY + offset,
187 | ltShader.cornerRadius * 2 + startX - offset,
188 | startY + ltShader.cornerRadius * 2 - offset
189 | ), 180f, 90f
190 | )
191 | strokePath.lineTo(viewWidth - stopX - rtShader.cornerRadius, startY + offset)
192 | strokePath.arcTo(
193 | RectF(
194 | viewWidth - stopX - rtShader.cornerRadius * 2 + offset,
195 | startY + offset,
196 | viewWidth - stopX - offset,
197 | startY + rtShader.cornerRadius * 2 - offset
198 | ), 270f, 90f
199 | )
200 | strokePath.lineTo(
201 | viewWidth - stopX - offset,
202 | viewHeight - stopY - rbShader.cornerRadius
203 | )
204 | strokePath.arcTo(
205 | RectF(
206 | viewWidth - stopX - rbShader.cornerRadius * 2 + offset,
207 | viewHeight - stopY - rbShader.cornerRadius * 2 + offset,
208 | viewWidth - stopX - offset,
209 | viewHeight - stopY - offset
210 | ), 0f, 90f
211 | )
212 | strokePath.lineTo(startX + lbShader.cornerRadius, viewHeight - stopY - offset)
213 | strokePath.arcTo(
214 | RectF(
215 | startX + offset,
216 | viewHeight - stopY - lbShader.cornerRadius * 2 + offset,
217 | startX + lbShader.cornerRadius * 2 - offset,
218 | viewHeight - stopY - offset
219 | ), 90f, 90f
220 | )
221 | strokePath.close()
222 | }
223 | } else {
224 | if (paperCorner == 0f) {
225 | path.moveTo(startX, startY)
226 | path.lineTo(viewWidth - stopX, startY)
227 | path.lineTo(viewWidth - stopX, viewHeight - stopY)
228 | path.lineTo(startX, viewHeight - stopY)
229 | } else {
230 | path.moveTo(startX, startY + paperCorner)
231 | path.arcTo(RectF(startX, startY, paperCorner * 2 + startX, startY + paperCorner * 2), 180f, 90f)
232 | path.lineTo(viewWidth - stopX - paperCorner, startY)
233 | path.arcTo(RectF(viewWidth - stopX - paperCorner * 2, startY, viewWidth - stopX, startY + paperCorner * 2), 270f, 90f)
234 | path.lineTo(viewWidth - stopX, viewHeight - stopY - paperCorner)
235 | path.arcTo(RectF(viewWidth - stopX - paperCorner * 2, viewHeight - stopY - paperCorner * 2, viewWidth - stopX, viewHeight - stopY), 0f, 90f)
236 | path.lineTo(startX + paperCorner, viewHeight - stopY)
237 | path.arcTo(RectF(startX, viewHeight - stopY - paperCorner * 2, startX + paperCorner * 2, viewHeight - stopY), 90f, 90f)
238 | }
239 | }
240 | path.close()
241 | }
242 |
243 | fun createDrawables(cardView: LCardView, shadowSize: Int) {
244 | val paddingLeft = cardView.paddingLeft
245 | val paddingRight = cardView.paddingRight
246 | val paddingTop = cardView.paddingTop
247 | val paddingBottom = cardView.paddingBottom
248 | val viewHeight = cardView.viewHeight
249 | val viewWidth = cardView.viewWidth
250 | val effectLeftOffset = cardView.effectLeftOffset
251 | val effectTopOffset = cardView.effectTopOffset
252 | val effectRightOffset = cardView.effectRightOffset
253 | val effectBottomOffset = cardView.effectBottomOffset
254 | val ltRadius = shadowSize + ltShader.cornerRadius
255 | val leftShadowDecrement = cardView.leftShadowDecrement.coerceAtMost(shadowSize.toFloat())
256 | val topShadowDecrement = cardView.topShadowDecrement.coerceAtMost(shadowSize.toFloat())
257 | val rightShadowDecrement = cardView.rightShadowDecrement.coerceAtMost(shadowSize.toFloat())
258 | val bottomShadowDecrement = cardView.bottomShadowDecrement.coerceAtMost(shadowSize.toFloat())
259 |
260 | if (ltRadius == 0f) {
261 | ltShader.frame.setEmpty()
262 | } else {
263 | val centerX: Float = if (paddingLeft > 0) ltRadius else ltRadius - effectLeftOffset
264 | val centerY: Float = if (paddingTop > 0) ltRadius else ltRadius - effectTopOffset
265 | ltShader.setDecrement(leftShadowDecrement, topShadowDecrement)
266 | ltShader.frame[centerX - ltRadius, centerY - ltRadius, centerX + ltRadius] = centerY + ltRadius
267 | ltShader.onFrameChange(centerX, centerY, ltRadius)
268 | }
269 | val rtRadius = shadowSize + rtShader.cornerRadius
270 | if (rtRadius == 0f) {
271 | rtShader.frame.setEmpty()
272 | } else {
273 | val centerX: Float = if (paddingRight > 0) viewWidth - rtRadius else viewWidth - rtRadius + effectRightOffset
274 | val centerY: Float = if (paddingTop > 0) rtRadius else rtRadius - effectTopOffset
275 | rtShader.setDecrement(rightShadowDecrement, topShadowDecrement)
276 | rtShader.frame[centerX - rtRadius, centerY - rtRadius, centerX + rtRadius] = centerY + rtRadius
277 | rtShader.onFrameChange(centerX, centerY, rtRadius)
278 | }
279 | val rbRadius = shadowSize + rbShader.cornerRadius
280 | if (rbRadius == 0f) {
281 | rbShader.frame.setEmpty()
282 | } else {
283 | val centerX: Float = if (paddingRight > 0) viewWidth - rbRadius else viewWidth - rbRadius + effectRightOffset
284 | val centerY: Float = if (paddingBottom > 0) viewHeight - rbRadius else viewHeight - rbRadius + effectBottomOffset
285 | rbShader.setDecrement(rightShadowDecrement, bottomShadowDecrement)
286 | rbShader.frame[centerX - rbRadius, centerY - rbRadius, centerX + rbRadius] = centerY + rbRadius
287 | rbShader.onFrameChange(centerX, centerY, rbRadius)
288 | }
289 | val lbRadius = shadowSize + lbShader.cornerRadius
290 | if (lbRadius == 0f) {
291 | lbShader.frame.setEmpty()
292 | } else {
293 | val centerX: Float = if (paddingLeft > 0) lbRadius else lbRadius - effectLeftOffset
294 | val centerY: Float = if (paddingBottom > 0) viewHeight - lbRadius else viewHeight - lbRadius + effectBottomOffset
295 | lbShader.setDecrement(leftShadowDecrement, bottomShadowDecrement)
296 | lbShader.frame[centerX - lbRadius, centerY - lbRadius, centerX + lbRadius] = centerY + lbRadius
297 | lbShader.onFrameChange(centerX, centerY, lbRadius)
298 | }
299 | var left = if (paddingLeft > 0) ltRadius else ltRadius - effectLeftOffset
300 | var right = if (paddingRight > 0) (viewWidth - rtRadius) else viewWidth - rtRadius + effectRightOffset
301 | var top: Float = if (paddingTop > 0) 0f else -effectTopOffset
302 | var bottom = top + shadowSize
303 | tShadow.setDecrement(0f, topShadowDecrement)
304 | tShadow.frame[left, top, right] = bottom
305 | tShadow.onFrameChange()
306 | right =
307 | if (paddingRight > 0) viewWidth.toFloat() else viewWidth + effectRightOffset
308 | left = right - shadowSize
309 | top = if (paddingTop > 0) rtRadius else rtRadius - effectTopOffset
310 | bottom =
311 | if (paddingBottom > 0) (viewHeight - rbRadius) else viewHeight - rbRadius + effectBottomOffset
312 | rShadow.setDecrement(rightShadowDecrement,0f)
313 | rShadow.frame[left, top, right] = bottom
314 | rShadow.onFrameChange()
315 | left = if (paddingLeft > 0) lbRadius else lbRadius - effectLeftOffset
316 | right =
317 | if (paddingRight > 0) (viewWidth - rbRadius) else viewWidth - rbRadius + effectRightOffset
318 | top =
319 | if (paddingBottom > 0) (viewHeight - shadowSize).toFloat() else viewHeight - shadowSize + effectBottomOffset
320 | bottom = top + shadowSize
321 | bShadow.setDecrement(0f,bottomShadowDecrement)
322 | bShadow.frame[left, top, right] = bottom
323 | bShadow.onFrameChange()
324 | right =
325 | if (paddingLeft > 0) shadowSize.toFloat() else shadowSize.toFloat() - effectLeftOffset
326 | left = right - shadowSize
327 | top = if (paddingTop > 0) ltRadius else ltRadius - effectTopOffset
328 | bottom =
329 | if (paddingBottom > 0) (viewHeight - lbRadius) else viewHeight - lbRadius + effectBottomOffset
330 | lShadow.setDecrement(leftShadowDecrement,0f)
331 | lShadow.frame[left, top, right] = bottom
332 | lShadow.onFrameChange()
333 |
334 | ltShader.reset()
335 | lbShader.reset()
336 | rtShader.reset()
337 | rbShader.reset()
338 | var ltChange = false
339 | var lbChange = false
340 | var rtChange = false
341 | var rbChange = false
342 | if (paddingLeft < lShadow.frame.width()) {
343 | if (tShadow.frame.width() <= 0f) {
344 | ltShader.offsetRight = lShadow.frame.width() - paddingLeft
345 | ltChange = true
346 | }
347 | if (bShadow.frame.width() <= 0f) {
348 | lbShader.offsetRight = lShadow.frame.width() - paddingLeft
349 | lbChange = true
350 | }
351 | }
352 | if (paddingTop < tShadow.frame.height()) {
353 | if (lShadow.frame.height() <= 0f) {
354 | ltShader.offsetBottom = tShadow.frame.height() - paddingTop
355 | ltChange = true
356 | }
357 | if (rShadow.frame.height() <= 0f) {
358 | rtShader.offsetBottom = tShadow.frame.height() - paddingTop
359 | rtChange = true
360 | }
361 | }
362 | if (paddingRight < rShadow.frame.width()) {
363 | if (tShadow.frame.width() <= 0f) {
364 | rtShader.offsetLeft = rShadow.frame.width() - paddingRight
365 | rtChange = true
366 | }
367 | if (bShadow.frame.width() <= 0f) {
368 | rbShader.offsetLeft = rShadow.frame.width() - paddingRight
369 | rbChange = true
370 | }
371 | }
372 | if (paddingBottom < bShadow.frame.height()) {
373 | if (lShadow.frame.height() <= 0f) {
374 | lbShader.offsetTop = bShadow.frame.height() - paddingBottom
375 | lbChange = true
376 | }
377 | if (rShadow.frame.height() <= 0f) {
378 | rbShader.offsetTop = bShadow.frame.height() - paddingBottom
379 | rbChange = true
380 | }
381 | }
382 | if (lbChange) {
383 | lbShader.makePath()
384 | }
385 | if (rbChange) {
386 | rbShader.makePath()
387 | }
388 | if (ltChange) {
389 | ltShader.makePath()
390 | }
391 | if (rtChange) {
392 | rtShader.makePath()
393 | }
394 | }
395 |
396 | fun setCornerRadius(value: Float, part: Int) {
397 | when (part) {
398 | IShadow.LEFT_TOP -> ltShader.cornerRadius = value
399 | IShadow.RIGHT_TOP -> rtShader.cornerRadius = value
400 | IShadow.RIGHT_BOTTOM -> rbShader.cornerRadius = value
401 | IShadow.LEFT_BOTTOM -> lbShader.cornerRadius = value
402 | }
403 | }
404 |
405 | fun setCurvature(curvature: Float) {
406 | bShadow.curvature = curvature
407 | }
408 |
409 | fun onDraw(canvas: Canvas, mPath: Path, paint: Paint) {
410 | shaderArray.forEach {
411 | it.draw(canvas, mPath, paint)
412 | }
413 | }
414 |
415 | fun markColorChange() {
416 | shaderArray.forEach {
417 | it.markColorChange()
418 | }
419 | }
420 |
421 | fun onShapeModeChange(shape: Int): Boolean {
422 | if (shape != LCardView.ADSORPTION && shape != LCardView.LINEAR) {
423 | return false
424 | }
425 | if (fluidShape == shape) {
426 | return false
427 | }
428 | fluidShape = shape
429 | shaderArray.forEach {
430 | it.onShapeModeChange(fluidShape)
431 | }
432 | return true
433 | }
434 |
435 | fun setBookRadius(r: Float) {
436 | bShadow.bookRadius = r
437 | }
438 |
439 | fun onDestroy() {
440 | shaderArray.forEach {
441 | it.onDestroy()
442 | }
443 | }
444 |
445 | fun onAttachedToWindow() {
446 | shaderArray.forEach {
447 | it.onAttachedToWindow()
448 | }
449 | }
450 |
451 | fun onDetachedFromWindow() {
452 | shaderArray.forEach {
453 | it.onDetachedFromWindow()
454 | }
455 | }
456 |
457 | fun bindLifeCircle(context: Context): Boolean {
458 | if (context !is Activity) {
459 | Log.w("LCardView", "非Activity无法绑定生命周期")
460 | return false
461 | }
462 | if (lifeCirCleSupport && context is LifecycleOwner) {
463 | context.lifecycle.addObserver(
464 | object : LifecycleEventObserver {
465 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
466 | if (event == Lifecycle.Event.ON_DESTROY) {
467 | source.lifecycle.removeObserver(this)
468 | onDestroy()
469 | }
470 | }
471 | })
472 | return true
473 | }
474 |
475 | val applicationContext = context.applicationContext
476 | if (applicationContext is Application) {
477 | applicationContext.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
478 | override fun onActivityPaused(activity: Activity) {
479 | }
480 |
481 | override fun onActivityResumed(activity: Activity) {
482 | }
483 |
484 | override fun onActivityStarted(activity: Activity) {
485 | }
486 |
487 | override fun onActivityDestroyed(activity: Activity) {
488 | if (activity == context) {
489 | applicationContext.unregisterActivityLifecycleCallbacks(this)
490 | onDestroy()
491 | }
492 | }
493 |
494 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
495 | }
496 |
497 | override fun onActivityStopped(activity: Activity) {
498 | }
499 |
500 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
501 | }
502 |
503 | })
504 | return true
505 | }
506 | return false
507 | }
508 | }
--------------------------------------------------------------------------------
/kotlin/src/main/java/www/linwg/org/lib/ShadowPool.kt:
--------------------------------------------------------------------------------
1 | package www.linwg.org.lib
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Color
5 | import android.graphics.Shader
6 | import android.util.Log
7 | import java.util.*
8 | import kotlin.collections.set
9 |
10 | class ShadowPool private constructor() {
11 |
12 | companion object {
13 | /**
14 | * Shader cache size
15 | */
16 | private var currentSize: Int = 0
17 |
18 | /**
19 | * Max shader cache size.
20 | */
21 | private const val maxSize: Int = 8 * 12
22 |
23 | /**
24 | * Dirty bitmap pool size
25 | */
26 | private var currentDirtySize: Int = 0
27 |
28 | /**
29 | * Max dirty size in bitmap pool. 16MB
30 | */
31 | private const val maxDirtySize: Int = 16 * 1024 * 1024
32 |
33 | /**
34 | * Linear mesh bitmap pool size
35 | */
36 | private var currentMeshSize: Int = 0
37 |
38 | /**
39 | * Max linear mesh size in bitmap pool size. 8MB
40 | */
41 | private const val maxMeshSize: Int = 8 * 1024 * 1024
42 |
43 | /**
44 | * Radial mesh bitmap pool size
45 | */
46 | private var currentMeshRadialSize: Int = 0
47 |
48 | /**
49 | * Max radial mesh size in bitmap pool size. 16MB
50 | */
51 | private const val maxMeshRadialSize: Int = 16 * 1024 * 1024
52 | private val head: Entry = Entry(Key(), null)
53 | private val linearShadowPool: HashMap> = HashMap()
54 | private val linearKeyQueue = ArrayDeque(20)
55 | private val radialKeyQueue = ArrayDeque(20)
56 | private val dirtyKeyQueue = ArrayDeque(20)
57 | private val meshKeyQueue = ArrayDeque(20)
58 | private val meshRadialKeyQueue = ArrayDeque(20)
59 |
60 | private val dirtyBitmapHead: Entry =
61 | Entry(DirtyBitmapKey(), null)
62 | private val dirtyBitmapPool: HashMap> =
63 | HashMap()
64 |
65 | private val meshBitmapHead: Entry = Entry(MeshBitmapKey(), null)
66 | private val meshBitmapPool: HashMap> =
67 | HashMap()
68 |
69 | private val meshRadialBitmapHead: Entry =
70 | Entry(MeshRadialBitmapKey(), null)
71 | private val meshRadialBitmapPool: HashMap> =
72 | HashMap()
73 |
74 | private fun getMeshKey(
75 | width: Int,
76 | height: Int,
77 | curvature: Int,
78 | bookRadius: Float = 0f,
79 | isLinear: Boolean,
80 | color: Int
81 | ): MeshBitmapKey {
82 | var key = meshKeyQueue.poll()
83 | if (key == null) {
84 | key = MeshBitmapKey(width, height, curvature, bookRadius, isLinear, color)
85 | } else {
86 | key.init(width, height, curvature, bookRadius, isLinear, color)
87 | }
88 | return key
89 | }
90 |
91 | fun putMesh(
92 | width: Int,
93 | height: Int,
94 | curvature: Int,
95 | color: Int,
96 | bookRadius: Float = 0f,
97 | isLinear: Boolean,
98 | bitmap: Bitmap
99 | ) {
100 | val key = getMeshKey(width, height, curvature, bookRadius, isLinear, color)
101 | var entry = meshBitmapPool[key]
102 | if (entry == null) {
103 | val v = BitmapEntry(bitmap, bitmap.allocationByteCount)
104 | entry = Entry(key, v)
105 | meshBitmapPool[key] = entry
106 | currentMeshSize += v.size
107 | } else {
108 | if (meshKeyQueue.size <= 20) {
109 | meshKeyQueue.offer(key)
110 | }
111 | breakEntry(entry)
112 | }
113 | entry.next = meshBitmapHead
114 | entry.prev = meshBitmapHead.prev
115 | ensureEntry(entry)
116 | trimMesh()
117 | }
118 |
119 | fun getMesh(
120 | width: Int,
121 | height: Int,
122 | curvature: Int,
123 | bookRadius: Float = 0f,
124 | isLinear: Boolean,
125 | color: Int
126 | ): Bitmap? {
127 | val key = getMeshKey(width, height, curvature, bookRadius, isLinear, color)
128 | val entry = meshBitmapPool[key] ?: return null
129 | if (meshKeyQueue.size <= 20) {
130 | meshKeyQueue.offer(key)
131 | }
132 | breakEntry(entry)
133 | entry.next = meshBitmapHead.next
134 | entry.prev = meshBitmapHead
135 | ensureEntry(entry)
136 | return entry.value?.value
137 | }
138 |
139 | private fun trimMesh() {
140 | while (currentMeshSize > maxMeshSize) {
141 | val last = meshBitmapHead.prev
142 | if (last == null || last == meshBitmapHead) {
143 | return
144 | }
145 | breakEntry(last)
146 | meshBitmapPool.remove(last.key)
147 | if (meshKeyQueue.size <= 20) {
148 | meshKeyQueue.offer(last.key)
149 | }
150 | putDirty(last.value!!.value, true)
151 | Log.i("LCardView", "ShadowPool trim one mesh bitmap and put it to the dirty pool.")
152 | currentMeshSize -= last.value.size
153 | }
154 | }
155 |
156 | private fun getMeshRadialKey(
157 | width: Int,
158 | height: Int,
159 | widthDecrement: Float,
160 | heightDecrement: Float,
161 | part: Int,
162 | color: Int
163 | ): MeshRadialBitmapKey {
164 | var key = meshRadialKeyQueue.poll()
165 | if (key == null) {
166 | key =
167 | MeshRadialBitmapKey(width, height, widthDecrement, heightDecrement, part, color)
168 | } else {
169 | key.init(width, height, widthDecrement, heightDecrement, part, color)
170 | }
171 | return key
172 | }
173 |
174 | fun putMeshRadial(
175 | width: Int,
176 | height: Int,
177 | widthDecrement: Float,
178 | heightDecrement: Float,
179 | part: Int,
180 | color: Int,
181 | bitmap: Bitmap
182 | ) {
183 | val key = getMeshRadialKey(width, height, widthDecrement, heightDecrement, part, color)
184 | var entry = meshRadialBitmapPool[key]
185 | if (entry == null) {
186 | val v = BitmapEntry(bitmap, bitmap.allocationByteCount)
187 | entry = Entry(key, v)
188 | meshRadialBitmapPool[key] = entry
189 | currentMeshRadialSize += v.size
190 | } else {
191 | if (meshRadialKeyQueue.size <= 20) {
192 | meshRadialKeyQueue.offer(key)
193 | }
194 | breakEntry(entry)
195 | }
196 | entry.next = meshRadialBitmapHead
197 | entry.prev = meshRadialBitmapHead.prev
198 | ensureEntry(entry)
199 | trimMeshRadial()
200 | }
201 |
202 | fun getMeshRadial(
203 | width: Int,
204 | height: Int,
205 | widthDecrement: Float,
206 | heightDecrement: Float,
207 | part: Int,
208 | color: Int
209 | ): Bitmap? {
210 | val key = getMeshRadialKey(width, height, widthDecrement, heightDecrement, part, color)
211 | val entry = meshRadialBitmapPool[key] ?: return null
212 | if (meshRadialKeyQueue.size <= 20) {
213 | meshRadialKeyQueue.offer(key)
214 | }
215 | breakEntry(entry)
216 | entry.next = meshRadialBitmapHead.next
217 | entry.prev = meshRadialBitmapHead
218 | ensureEntry(entry)
219 | return entry.value?.value
220 | }
221 |
222 | private fun trimMeshRadial() {
223 | while (currentMeshRadialSize > maxMeshRadialSize) {
224 | val last = meshRadialBitmapHead.prev
225 | if (last == null || last == meshRadialBitmapHead) {
226 | return
227 | }
228 | breakEntry(last)
229 | meshRadialBitmapPool.remove(last.key)
230 | if (meshRadialKeyQueue.size <= 20) {
231 | meshRadialKeyQueue.offer(last.key)
232 | }
233 | putDirty(last.value!!.value, true)
234 | Log.i("LCardView", "ShadowPool trim one mesh bitmap and put it to the dirty pool.")
235 | currentMeshRadialSize -= last.value.size
236 | }
237 | }
238 |
239 | private fun getDirtyKey(width: Int, height: Int, isMesh: Boolean): DirtyBitmapKey {
240 | var key = dirtyKeyQueue.poll()
241 | if (key == null) {
242 | key = DirtyBitmapKey(width, height, isMesh)
243 | } else {
244 | key.init(width, height, isMesh)
245 | }
246 | return key
247 | }
248 |
249 | fun putDirty(bitmap: Bitmap, isMesh: Boolean = false) {
250 | val key = getDirtyKey(bitmap.width, bitmap.height, isMesh)
251 | var entry = dirtyBitmapPool[key]
252 | if (entry == null) {
253 | val v = BitmapEntry(bitmap, bitmap.allocationByteCount)
254 | entry = Entry(key, v)
255 | dirtyBitmapPool[key] = entry
256 | currentDirtySize += v.size
257 | } else {
258 | breakEntry(entry)
259 | if (dirtyKeyQueue.size <= 20) {
260 | dirtyKeyQueue.offer(key)
261 | }
262 | }
263 | entry.next = dirtyBitmapHead
264 | entry.prev = dirtyBitmapHead.prev
265 | ensureEntry(entry)
266 | trimDirty()
267 | }
268 |
269 | fun getDirty(width: Int, height: Int, isMesh: Boolean = false): Bitmap {
270 | val key = getDirtyKey(width, height, isMesh)
271 | val entry = dirtyBitmapPool[key] ?: return Bitmap.createBitmap(
272 | width, height, Bitmap.Config.ARGB_8888
273 | )
274 | if (dirtyKeyQueue.size <= 20) {
275 | dirtyKeyQueue.offer(key)
276 | }
277 | breakEntry(entry)
278 | dirtyBitmapPool.remove(key)
279 | entry.value?.value?.eraseColor(Color.TRANSPARENT)
280 | return entry.value!!.value
281 | }
282 |
283 | private fun trimDirty() {
284 | while (currentDirtySize > maxDirtySize) {
285 | val last = dirtyBitmapHead.prev
286 | if (last == null || last == dirtyBitmapHead) {
287 | return
288 | }
289 | breakEntry(last)
290 | if (dirtyKeyQueue.size <= 20) {
291 | dirtyKeyQueue.offer(last.key)
292 | }
293 | dirtyBitmapPool.remove(last.key)
294 | last.value?.value?.recycle()
295 | Log.i("LCardView", "ShadowPool trim one dirty bitmap.")
296 | currentDirtySize -= last.value!!.size
297 | }
298 | }
299 |
300 | fun getLinearKey(
301 | width: Int,
302 | height: Int,
303 | widthDecrement: Float,
304 | heightDecrement: Float,
305 | mode: Int,
306 | part: Int,
307 | color: Int
308 | ): LinearKey {
309 | var key = linearKeyQueue.poll()
310 | if (key == null) {
311 | key = LinearKey()
312 | }
313 | key.init(width, height, widthDecrement, heightDecrement, mode, part, color)
314 | return key
315 | }
316 |
317 | fun getRadialKey(
318 | width: Int, height: Int, mode: Int, part: Int, cornerRadius: Float, color: Int
319 | ): RadialKey {
320 | var key = radialKeyQueue.poll()
321 | if (key == null) {
322 | key = RadialKey()
323 | }
324 | key.init(width, height, mode, part, cornerRadius, color)
325 | return key
326 | }
327 |
328 |
329 | fun put(key: Key, shadow: Shader) {
330 | var entry = linearShadowPool[key]
331 | if (entry == null) {
332 | entry = Entry(key, shadow)
333 | linearShadowPool[key] = entry
334 | currentSize++
335 | } else {
336 | breakEntry(entry)
337 | if (key is LinearKey) {
338 | if (linearKeyQueue.size <= 20) {
339 | linearKeyQueue.offer(key)
340 | }
341 | }
342 | if (key is RadialKey) {
343 | if (radialKeyQueue.size <= 20) {
344 | radialKeyQueue.offer(key)
345 | }
346 | }
347 | }
348 | entry.next = head
349 | entry.prev = head.prev
350 | ensureEntry(entry)
351 | trim()
352 | }
353 |
354 | fun get(key: Key): Shader? {
355 | val entry = linearShadowPool[key] ?: return null
356 | if (key is LinearKey) {
357 | if (linearKeyQueue.size <= 20) {
358 | linearKeyQueue.offer(key)
359 | }
360 | }
361 | if (key is RadialKey) {
362 | if (radialKeyQueue.size <= 20) {
363 | radialKeyQueue.offer(key)
364 | }
365 | }
366 | breakEntry(entry)
367 | entry.next = head.next
368 | entry.prev = head
369 | ensureEntry(entry)
370 | return entry.value
371 | }
372 |
373 | private fun trim() {
374 | while (currentSize > maxSize) {
375 | val last = head.prev
376 | if (last == null || last == head) {
377 | return
378 | }
379 | if (last.key is LinearKey) {
380 | if (linearKeyQueue.size <= 20) {
381 | linearKeyQueue.offer(last.key)
382 | }
383 | }
384 | if (last.key is RadialKey) {
385 | if (radialKeyQueue.size <= 20) {
386 | radialKeyQueue.offer(last.key)
387 | }
388 | }
389 | breakEntry(last)
390 | linearShadowPool.remove(last.key)
391 | Log.i("LCardView", "ShadowPool trim one shadow.")
392 | currentSize--
393 | }
394 | }
395 |
396 | private fun breakEntry(entry: Entry) {
397 | entry.next?.prev = entry.prev
398 | entry.prev?.next = entry.next
399 | }
400 |
401 | private fun ensureEntry(entry: Entry) {
402 | entry.next?.prev = entry
403 | entry.prev?.next = entry
404 | }
405 | }
406 | }
407 |
408 | class Entry(val key: K, val value: V?) {
409 | var prev: Entry? = null
410 | var next: Entry? = null
411 | }
412 |
413 | internal class BitmapEntry constructor(val value: Bitmap, val size: Int)
414 |
415 | open class Key
416 |
417 | data class LinearKey(
418 | var width: Int = 0,
419 | var height: Int = 0,
420 | var widthDecrement: Float = 0f,
421 | var heightDecrement: Float = 0f,
422 | var mode: Int = 0,
423 | var part: Int = 0,
424 | var startColor: Int = 0
425 | ) : Key() {
426 | fun init(
427 | width: Int,
428 | height: Int,
429 | widthDecrement: Float,
430 | heightDecrement: Float,
431 | mode: Int,
432 | part: Int,
433 | color: Int
434 | ) {
435 | this.width = width
436 | this.height = height
437 | this.widthDecrement = widthDecrement
438 | this.heightDecrement = heightDecrement
439 | this.mode = mode
440 | this.part = part
441 | this.startColor = color
442 | }
443 | }
444 |
445 | data class RadialKey(
446 | var width: Int = 0,
447 | var height: Int = 0,
448 | var mode: Int = 0,
449 | var part: Int = 0,
450 | var cornerRadius: Float = 0f,
451 | var startColor: Int = 0
452 | ) : Key() {
453 | fun init(width: Int, height: Int, mode: Int, part: Int, cornerRadius: Float, color: Int) {
454 | this.width = width
455 | this.height = height
456 | this.mode = mode
457 | this.part = part
458 | this.cornerRadius = cornerRadius
459 | this.startColor = color
460 | }
461 | }
462 |
463 | data class DirtyBitmapKey(var width: Int = 0, var height: Int = 0, var isMesh: Boolean = false) : Key() {
464 | fun init(width: Int, height: Int, isMesh: Boolean = false) {
465 | this.width = width
466 | this.height = height
467 | this.isMesh = isMesh
468 | }
469 | }
470 |
471 | data class MeshBitmapKey(
472 | var width: Int = 0,
473 | var height: Int = 0,
474 | var curvature: Int = 0,
475 | var bookRadius: Float = 0f,
476 | var isLinear: Boolean = false,
477 | var startColor: Int = 0
478 | ) : Key() {
479 | fun init(
480 | width: Int,
481 | height: Int,
482 | curvature: Int,
483 | bookRadius: Float = 0f,
484 | isLinear: Boolean,
485 | color: Int
486 | ) {
487 | this.width = width
488 | this.height = height
489 | this.curvature = curvature
490 | this.bookRadius = bookRadius
491 | this.isLinear = isLinear
492 | this.startColor = color
493 | }
494 | }
495 |
496 | data class MeshRadialBitmapKey(
497 | var width: Int = 0,
498 | var height: Int = 0,
499 | var widthDecrement: Float = 0f,
500 | var heightDecrement: Float = 0f,
501 | var part: Int = 0,
502 | var startColor: Int = 0
503 | ) : Key() {
504 | fun init(
505 | width: Int,
506 | height: Int,
507 | widthDecrement: Float,
508 | heightDecrement: Float,
509 | part: Int,
510 | color: Int
511 | ) {
512 | this.width = width
513 | this.height = height
514 | this.widthDecrement = widthDecrement
515 | this.heightDecrement = heightDecrement
516 | this.part = part
517 | this.startColor = color
518 | }
519 | }
--------------------------------------------------------------------------------
/kotlin/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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/lib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/lib/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | }
4 |
5 | android {
6 | compileSdk 33
7 |
8 | publishNonDefault true
9 |
10 | defaultConfig {
11 | minSdk 21
12 | targetSdk 33
13 | versionCode 1
14 | versionName "1.0"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | lintOptions {
25 | abortOnError false
26 | }
27 |
28 | }
29 |
30 | dependencies {
31 | implementation fileTree(dir: 'libs', include: ['*.jar'])
32 | implementation 'com.android.support:appcompat-v7:28.0.0'
33 | }
34 |
--------------------------------------------------------------------------------
/lib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/lib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/lib/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 |
--------------------------------------------------------------------------------
/screenshot/book.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/screenshot/book.gif
--------------------------------------------------------------------------------
/screenshot/cn.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/screenshot/cn.gif
--------------------------------------------------------------------------------
/screenshot/color.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/screenshot/color.gif
--------------------------------------------------------------------------------
/screenshot/demo.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/screenshot/demo.apk
--------------------------------------------------------------------------------
/screenshot/ele.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/screenshot/ele.gif
--------------------------------------------------------------------------------
/screenshot/list.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/screenshot/list.gif
--------------------------------------------------------------------------------
/screenshot/mesh.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/screenshot/mesh.gif
--------------------------------------------------------------------------------
/screenshot/offset.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/screenshot/offset.gif
--------------------------------------------------------------------------------
/screenshot/sync.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linwg1988/LCardView/af962bef0886a5507a96b51580687383ef7f2e0b/screenshot/sync.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':kotlin'
2 | include ':app', ':lib'
3 |
--------------------------------------------------------------------------------