├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── madreain
│ │ └── sku
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── Product.json
│ ├── java
│ │ └── com
│ │ │ └── madreain
│ │ │ └── sku
│ │ │ ├── AppGlideModule.java
│ │ │ ├── MainActivity.java
│ │ │ ├── ProductSkuDialog.java
│ │ │ ├── bean
│ │ │ ├── ProductData.java
│ │ │ ├── Sku.java
│ │ │ └── SkuAttribute.java
│ │ │ ├── utils
│ │ │ └── DisplayUtil.java
│ │ │ └── view
│ │ │ ├── FlowLayout.java
│ │ │ ├── OnSkuListener.java
│ │ │ ├── SkuItemLayout.java
│ │ │ ├── SkuItemView.java
│ │ │ ├── SkuMaxHeightScrollView.java
│ │ │ └── SkuSelectScrollView.java
│ └── res
│ │ ├── anim
│ │ ├── slide_in_from_bottom.xml
│ │ └── slide_out_to_bottom.xml
│ │ ├── color
│ │ ├── quantity_operator_text_selector.xml
│ │ └── sku_item_text_selector.xml
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ ├── shape_mfe5002.xml
│ │ ├── sku_item_selector.xml
│ │ ├── sku_quantity_input_selector.xml
│ │ ├── sku_quantity_minus_selector.xml
│ │ └── sku_quantity_plus_selector.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── dialog_sku.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── comm_close.png
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── comm_close.png
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── madreain
│ └── sku
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── settings.gradle
└── sku
└── sku.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android仿淘宝、京东商品选择器
2 |
3 | ### 效果图如下
4 |
5 | 
6 |
7 | ### sku相关数据
8 | SKU相关数据大致如下,其他参数可根据自身项目自由扩展
9 |
10 | ```
11 | {
12 | "pid": 10001,//商品id
13 | "pictureUrl"://商品主图
14 | "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109966&di=95d3036dc6282a14fda223c3b55284be&imgtype=0&src=http%3A%2F%2Fimg4.vipshop.com%2Fupload%2Fmerchandise%2F27600%2FZIMMUR-ZMCQ2028-2.jpg",
15 | "stockQuantity": 939,//商品总库存
16 | "maxPrice": 199.00,//最高价
17 | "minPrice": 699.00,//最低价
18 | "skus": [
19 | {
20 | "sid": 10001,//skuid
21 | "price": 199.00,//价格
22 | "pictureUrl": //商品图
23 | "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109966&di=95d3036dc6282a14fda223c3b55284be&imgtype=0&src=http%3A%2F%2Fimg4.vipshop.com%2Fupload%2Fmerchandise%2F27600%2FZIMMUR-ZMCQ2028-2.jpg",
24 | "stockQuantity": 133,//该商品库存
25 | "attributes": [
26 | {
27 | "value": "M",
28 | "key": "尺码"
29 | },
30 | {
31 | "value": "绿松石蓝色",
32 | "key": "颜色"
33 | }
34 | ]
35 | }
36 | ]
37 | }
38 |
39 | ```
40 |
41 | ### 相关设置
42 |
43 | 1.不同状态显示的设置
44 | sku_item_text_selector.xml sku单item字体显示的三种状态设置
45 | sku_item_selector.xml sku单item显示的三种状态设置
46 | quantity_operator_text_selector.xml 加减按钮的状态设置
47 | sku_quantity_input_selector.xml 输入框两种输入状态设置
48 | sku_quantity_minus_selector.xml 减按钮的背景状态设置
49 | sku_quantity_plus_selector.xml 加按钮的背景状态设置
50 |
51 | 2.最大高度、最小高度设置
52 | SkuMaxHeightScrollView中可在xml中设置maxSkuHeight、minSkuHeight,而SkuSelectScrollView继承自SkuMaxHeightScrollView也可直接设置
53 |
54 | ```
55 |
62 | ```
63 |
64 | ### 相关函数
65 |
66 | ```
67 | scrollSkuList.setOnSkuListener(new OnSkuListener() {
68 | @Override
69 | public void onUnselected(SkuAttribute unselectedAttribute) {
70 |
71 | }
72 |
73 | @Override
74 | public void onSelect(SkuAttribute selectAttribute) {
75 |
76 | }
77 |
78 | @Override
79 | public void onSkuSelected(Sku sku) {
80 |
81 | }
82 | });
83 | ```
84 |
85 | 根据不同业务场景对监听方法做处理
86 |
87 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.madreain.sku"
7 | minSdkVersion 16
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation 'com.android.support:appcompat-v7:28.0.0'
24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
28 | //ButterKnife
29 | implementation "com.jakewharton:butterknife:8.8.1"
30 | annotationProcessor "com.jakewharton:butterknife-compiler:8.8.1"
31 | //Glide https://github.com/bumptech/glide
32 | api "com.github.bumptech.glide:glide:4.6.1"
33 | annotationProcessor "com.github.bumptech.glide:compiler:4.6.1"
34 | //Gson
35 | api "com.google.code.gson:gson:2.8.2"
36 | }
37 |
--------------------------------------------------------------------------------
/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/androidTest/java/com/madreain/sku/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.madreain.sku", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/assets/Product.json:
--------------------------------------------------------------------------------
1 | {
2 | "pid": 10001,
3 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109966&di=95d3036dc6282a14fda223c3b55284be&imgtype=0&src=http%3A%2F%2Fimg4.vipshop.com%2Fupload%2Fmerchandise%2F27600%2FZIMMUR-ZMCQ2028-2.jpg",
4 | "stockQuantity": 939,
5 | "maxPrice": 199.0000,
6 | "minPrice": 699.0000,
7 | "skus": [
8 | {
9 | "sid": 10001,
10 | "price": 199.0000,
11 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109966&di=95d3036dc6282a14fda223c3b55284be&imgtype=0&src=http%3A%2F%2Fimg4.vipshop.com%2Fupload%2Fmerchandise%2F27600%2FZIMMUR-ZMCQ2028-2.jpg",
12 | "stockQuantity": 133,
13 | "attributes": [
14 | {
15 | "value": "M",
16 | "key": "尺码"
17 | },
18 | {
19 | "value": "绿松石蓝色",
20 | "key": "颜色"
21 | }
22 | ]
23 | },
24 | {
25 | "sid": 10002,
26 | "price": 399.0000,
27 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109966&di=76331db1c1a7b9edad2583bd95d7e634&imgtype=0&src=http%3A%2F%2Fi1.ygimg.cn%2Fpics%2Fnowhere%2F2013%2F99862821%2F99862821_01_l.jpg",
28 | "stockQuantity": 333,
29 | "attributes": [
30 | {
31 | "value": "M",
32 | "key": "尺码"
33 | },
34 | {
35 | "value": "灰色",
36 | "key": "颜色"
37 | }
38 | ]
39 | },
40 | {
41 | "sid": 10003,
42 | "price": 199.0000,
43 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109966&di=9d33c2c3adbf9d6af01f589777cd0bff&imgtype=0&src=http%3A%2F%2Fimg2.vipshop.com%2Fupload%2Fmerchandise%2F34424%2FZIMMUR-Z12CQW145WC-2.jpg",
44 | "stockQuantity": 23,
45 | "attributes": [
46 | {
47 | "value": "L",
48 | "key": "尺码"
49 | },
50 | {
51 | "value": "绿松石蓝色",
52 | "key": "颜色"
53 | }
54 | ]
55 | },
56 | {
57 | "sid": 10004,
58 | "price": 499.0000,
59 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109966&di=ada65d9d4dbbfaf5e46944f469c64ebf&imgtype=0&src=http%3A%2F%2Fwww.bjzwx.com%2Fuploadfile%2F2017%2F1219%2F20171219082705892.jpg",
60 | "stockQuantity": 323,
61 | "attributes": [
62 | {
63 | "value": "L",
64 | "key": "尺码"
65 | },
66 | {
67 | "value": "灰色",
68 | "key": "颜色"
69 | }
70 | ]
71 | },
72 | {
73 | "sid": 10005,
74 | "price": 699.0000,
75 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=158716506d68be6d18f033ab6a6518a7&imgtype=0&src=http%3A%2F%2Fa.vpimg3.com%2Fupload%2Fmerchandise%2F120170%2FOSA-SW332654065A-1.jpg",
76 | "stockQuantity": 3,
77 | "attributes": [
78 | {
79 | "value": "XL",
80 | "key": "尺码"
81 | },
82 | {
83 | "value": "粉红色",
84 | "key": "颜色"
85 | }
86 | ]
87 | },
88 | {
89 | "sid": 10006,
90 | "price": 699.0000,
91 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=f5851b42bed9833f5f9e2a8884d027d8&imgtype=0&src=http%3A%2F%2Fimg.11665.com%2Fimg04_p%2Fi4%2FT1EX43FrFnXXXXXXXX_%2521%25210-item_pic.jpg",
92 | "stockQuantity": 1,
93 | "attributes": [
94 | {
95 | "value": "L",
96 | "key": "尺码"
97 | },
98 | {
99 | "value": "粉红色",
100 | "key": "颜色"
101 | }
102 | ]
103 | },
104 | {
105 | "sid": 10007,
106 | "price": 699.0000,
107 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
108 | "stockQuantity": 123,
109 | "attributes": [
110 | {
111 | "value": "M",
112 | "key": "尺码"
113 | },
114 | {
115 | "value": "粉红色",
116 | "key": "颜色"
117 | }
118 | ]
119 | },
120 | {
121 | "sid": 10008,
122 | "price": 699.0000,
123 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
124 | "stockQuantity": 123,
125 | "attributes": [
126 | {
127 | "value": "S",
128 | "key": "尺码"
129 | },
130 | {
131 | "value": "粉红色",
132 | "key": "颜色"
133 | }
134 | ]
135 | },
136 | {
137 | "sid": 10010,
138 | "price": 699.0000,
139 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
140 | "stockQuantity": 123,
141 | "attributes": [
142 | {
143 | "value": "XS",
144 | "key": "尺码"
145 | },
146 | {
147 | "value": "粉红色",
148 | "key": "颜色"
149 | }
150 | ]
151 | },
152 | {
153 | "sid": 10011,
154 | "price": 699.0000,
155 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
156 | "stockQuantity": 123,
157 | "attributes": [
158 | {
159 | "value": "XXL",
160 | "key": "尺码"
161 | },
162 | {
163 | "value": "粉红色",
164 | "key": "颜色"
165 | }
166 | ]
167 | },
168 | {
169 | "sid": 10012,
170 | "price": 699.0000,
171 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
172 | "stockQuantity": 123,
173 | "attributes": [
174 | {
175 | "value": "XXXL",
176 | "key": "尺码"
177 | },
178 | {
179 | "value": "粉红色",
180 | "key": "颜色"
181 | }
182 | ]
183 | },
184 | {
185 | "sid": 10013,
186 | "price": 699.0000,
187 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
188 | "stockQuantity": 123,
189 | "attributes": [
190 | {
191 | "value": "XL",
192 | "key": "尺码"
193 | },
194 | {
195 | "value": "大红色",
196 | "key": "颜色"
197 | }
198 | ]
199 | },
200 | {
201 | "sid": 10014,
202 | "price": 699.0000,
203 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
204 | "stockQuantity": 123,
205 | "attributes": [
206 | {
207 | "value": "XL",
208 | "key": "尺码"
209 | },
210 | {
211 | "value": "橙色",
212 | "key": "颜色"
213 | }
214 | ]
215 | },
216 | {
217 | "sid": 10015,
218 | "price": 699.0000,
219 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
220 | "stockQuantity": 123,
221 | "attributes": [
222 | {
223 | "value": "XL",
224 | "key": "尺码"
225 | },
226 | {
227 | "value": "咖啡色",
228 | "key": "颜色"
229 | }
230 | ]
231 | },
232 | {
233 | "sid": 10016,
234 | "price": 699.0000,
235 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
236 | "stockQuantity": 123,
237 | "attributes": [
238 | {
239 | "value": "XL",
240 | "key": "尺码"
241 | },
242 | {
243 | "value": "橘黄色",
244 | "key": "颜色"
245 | }
246 | ]
247 | },
248 | {
249 | "sid": 10017,
250 | "price": 699.0000,
251 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
252 | "stockQuantity": 123,
253 | "attributes": [
254 | {
255 | "value": "XL",
256 | "key": "尺码"
257 | },
258 | {
259 | "value": "海蓝色",
260 | "key": "颜色"
261 | }
262 | ]
263 | },
264 | {
265 | "sid": 10018,
266 | "price": 699.0000,
267 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
268 | "stockQuantity": 123,
269 | "attributes": [
270 | {
271 | "value": "XL",
272 | "key": "尺码"
273 | },
274 | {
275 | "value": "宝蓝色",
276 | "key": "颜色"
277 | }
278 | ]
279 | },
280 | {
281 | "sid": 10019,
282 | "price": 699.0000,
283 | "pictureUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564393109965&di=fff806632f0c299b7a3572a3900360f4&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201411%2F07%2F20141107201918_8Q4fV.jpeg",
284 | "stockQuantity": 1,
285 | "attributes": [
286 | {
287 | "value": "XL",
288 | "key": "尺码"
289 | },
290 | {
291 | "value": "墨绿色",
292 | "key": "颜色"
293 | }
294 | ]
295 | }
296 | ]
297 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/AppGlideModule.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 |
6 | import com.bumptech.glide.GlideBuilder;
7 | import com.bumptech.glide.annotation.GlideModule;
8 | import com.bumptech.glide.load.DecodeFormat;
9 | import com.bumptech.glide.load.engine.DiskCacheStrategy;
10 | import com.bumptech.glide.request.RequestOptions;
11 |
12 | /**
13 | * @author madreain
14 | * @date 2019-07-29.
15 | * module:
16 | * description:
17 | */
18 | @GlideModule
19 | public class AppGlideModule extends com.bumptech.glide.module.AppGlideModule {
20 | @Override
21 | public boolean isManifestParsingEnabled() {
22 | return false;
23 | }
24 |
25 | @Override
26 | public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
27 | RequestOptions requestOptions = new RequestOptions()
28 | .centerCrop()
29 | .dontAnimate()
30 | .disallowHardwareConfig()
31 | .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)//表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。
32 | .format(DecodeFormat.PREFER_RGB_565);
33 | builder.setDefaultRequestOptions(requestOptions);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.TextView;
7 | import android.widget.Toast;
8 |
9 | import com.madreain.sku.bean.ProductData;
10 | import com.madreain.sku.bean.Sku;
11 |
12 | import butterknife.ButterKnife;
13 |
14 | /**
15 | * @author madreain
16 | * @date 2019-07-27.
17 | * module:
18 | * description:
19 | */
20 | public class MainActivity extends AppCompatActivity {
21 |
22 | private TextView tv;
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.activity_main);
27 | tv=findViewById(R.id.tv);
28 | tv.setOnClickListener(new View.OnClickListener() {
29 | @Override
30 | public void onClick(View v) {
31 | showSkuDialog();
32 | }
33 | });
34 | }
35 |
36 | //商品sku
37 | private ProductSkuDialog dialog;
38 |
39 | private void showSkuDialog() {
40 | if (dialog == null) {
41 | dialog = new ProductSkuDialog(this);
42 | dialog.setData(ProductData.get(this), new ProductSkuDialog.Callback() {
43 | @Override
44 | public void onAdded(Sku sku, int quantity) {
45 | //立即购买再跳转到
46 | Toast.makeText(MainActivity.this, "添加购物车成功 "+sku.toString(), Toast.LENGTH_SHORT).show();
47 | }
48 |
49 | @Override
50 | public void onSelect(String selected) {
51 | //默认设置 有选的
52 | tv.setText(selected);
53 | }
54 |
55 | @Override
56 | public void reUnSelect() {
57 |
58 | }
59 | });
60 | }
61 | dialog.show();
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/ProductSkuDialog.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.StyleRes;
7 | import android.text.TextUtils;
8 | import android.view.Gravity;
9 | import android.view.KeyEvent;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.view.Window;
13 | import android.view.WindowManager;
14 | import android.view.inputmethod.EditorInfo;
15 | import android.widget.EditText;
16 | import android.widget.ImageButton;
17 | import android.widget.ImageView;
18 | import android.widget.TextView;
19 | import android.widget.Toast;
20 |
21 | import com.madreain.sku.bean.ProductData;
22 | import com.madreain.sku.bean.Sku;
23 | import com.madreain.sku.bean.SkuAttribute;
24 | import com.madreain.sku.view.OnSkuListener;
25 | import com.madreain.sku.view.SkuSelectScrollView;
26 |
27 | import java.util.List;
28 |
29 | import butterknife.BindView;
30 | import butterknife.ButterKnife;
31 |
32 | /**
33 | * @author madreain
34 | * @date 2019-07-27.
35 | * module:
36 | * description:
37 | */
38 | public class ProductSkuDialog extends Dialog {
39 |
40 | @BindView(R.id.ib_sku_close)
41 | ImageButton ibSkuClose;
42 | @BindView(R.id.tv_sku_info)
43 | TextView tvSkuInfo;
44 | @BindView(R.id.tv_sku_selling_price)
45 | TextView tvSkuSellingPrice;
46 | @BindView(R.id.scroll_sku_list)
47 | SkuSelectScrollView scrollSkuList;
48 | @BindView(R.id.btn_submit)
49 | TextView btnSubmit;
50 | @BindView(R.id.iv_sku_logo)
51 | ImageView ivSkuLogo;
52 | @BindView(R.id.tv_sku_quantity_label)
53 | TextView tvSkuQuantityLabel;
54 | @BindView(R.id.tv_sku_quantity)
55 | TextView tvSkuQuantity;
56 | @BindView(R.id.btn_sku_quantity_minus)
57 | TextView btnSkuQuantityMinus;
58 | @BindView(R.id.et_sku_quantity_input)
59 | EditText etSkuQuantityInput;
60 | @BindView(R.id.btn_sku_quantity_plus)
61 | TextView btnSkuQuantityPlus;
62 |
63 |
64 | private Context context;
65 | private ProductData product;
66 | private List skuList;
67 | private Callback callback;
68 | private Sku selectedSku;
69 | private String priceFormat;
70 | private String stockQuantityFormat;
71 |
72 | public ProductSkuDialog(@NonNull Context context) {
73 | this(context, R.style.CommonBottomDialogStyle);
74 | }
75 |
76 | public ProductSkuDialog(@NonNull Context context, @StyleRes int themeResId) {
77 | super(context, themeResId);
78 | this.context = context;
79 | initView();
80 | }
81 |
82 | private void initView() {
83 | View view = View.inflate(context, R.layout.dialog_sku, null);
84 | Window window = this.getWindow();
85 | window.setContentView(view);
86 | ButterKnife.bind(this, view);
87 | //关闭
88 | ibSkuClose.setOnClickListener(new View.OnClickListener() {
89 | @Override
90 | public void onClick(View v) {
91 | dismiss();
92 | }
93 | });
94 | //减少
95 | btnSkuQuantityMinus.setOnClickListener(new View.OnClickListener() {
96 | @Override
97 | public void onClick(View v) {
98 | String quantity = etSkuQuantityInput.getText().toString();
99 | if (TextUtils.isEmpty(quantity)) {
100 | return;
101 | }
102 | int quantityInt = Integer.parseInt(quantity);
103 | if (quantityInt > 1) {
104 | String newQuantity = String.valueOf(quantityInt - 1);
105 | etSkuQuantityInput.setText(newQuantity);
106 | etSkuQuantityInput.setSelection(newQuantity.length());
107 | updateQuantityOperator(quantityInt - 1);
108 | }
109 | }
110 | });
111 | //增加
112 | btnSkuQuantityPlus.setOnClickListener(new View.OnClickListener() {
113 | @Override
114 | public void onClick(View v) {
115 | String quantity = etSkuQuantityInput.getText().toString();
116 | if (TextUtils.isEmpty(quantity) || selectedSku == null) {
117 | return;
118 | }
119 | int quantityInt = Integer.parseInt(quantity);
120 | if (quantityInt < selectedSku.getStockQuantity()) {
121 | String newQuantity = String.valueOf(quantityInt + 1);
122 | etSkuQuantityInput.setText(newQuantity);
123 | etSkuQuantityInput.setSelection(newQuantity.length());
124 | updateQuantityOperator(quantityInt + 1);
125 | }
126 | }
127 | });
128 | //输入
129 | etSkuQuantityInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
130 | @Override
131 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
132 | if (actionId != EditorInfo.IME_ACTION_DONE || selectedSku == null) {
133 | return false;
134 | }
135 | String quantity = etSkuQuantityInput.getText().toString();
136 | if (TextUtils.isEmpty(quantity)) {
137 | return false;
138 | }
139 | int quantityInt = Integer.parseInt(quantity);
140 | if (quantityInt <= 1) {
141 | etSkuQuantityInput.setText("1");
142 | etSkuQuantityInput.setSelection(1);
143 | updateQuantityOperator(1);
144 | } else if (quantityInt >= selectedSku.getStockQuantity()) {
145 | String newQuantity = String.valueOf(selectedSku.getStockQuantity());
146 | etSkuQuantityInput.setText(newQuantity);
147 | etSkuQuantityInput.setSelection(newQuantity.length());
148 | updateQuantityOperator(selectedSku.getStockQuantity());
149 | } else {
150 | etSkuQuantityInput.setSelection(quantity.length());
151 | updateQuantityOperator(quantityInt);
152 | }
153 | return false;
154 | }
155 | });
156 | //sku整体的点击事件
157 | scrollSkuList.setOnSkuListener(new OnSkuListener() {
158 | @Override
159 | public void onUnselected(SkuAttribute unselectedAttribute) {
160 | selectedSku = null;
161 | //默认第一张照片
162 | GlideApp.with(context).load(product.getPictureUrl()).into(ivSkuLogo);
163 | tvSkuQuantity.setText(String.format(stockQuantityFormat, product.getStockQuantity()));
164 | String selected = getSelected();
165 | //设置回掉
166 | callback.onSelect(selected);
167 | //增加前缀
168 | if (isHaveSelect()) {
169 | selected = "已选: " + selected;
170 | } else {
171 | selected = "请选择: " + selected;
172 | tvSkuSellingPrice.setText(String.format(priceFormat, product.getMinPrice()));
173 | }
174 | tvSkuInfo.setText(selected);
175 | btnSubmit.setEnabled(false);
176 | etSkuQuantityInput.setText("1");
177 | updateQuantityOperator(1);
178 | }
179 |
180 | @Override
181 | public void onSelect(SkuAttribute selectAttribute) {
182 | String selected = getSelected();
183 | //设置回掉
184 | callback.onSelect(selected);
185 | //增加前缀
186 | if (isHaveSelect()) {
187 | selected = "已选: " + selected;
188 | } else {
189 | selected = "请选择: " + selected;
190 | tvSkuSellingPrice.setText(String.format(priceFormat, product.getMinPrice()));
191 | }
192 | tvSkuInfo.setText(selected);
193 | etSkuQuantityInput.setText("1");
194 | updateQuantityOperator(1);
195 | }
196 |
197 | @Override
198 | public void onSkuSelected(Sku sku) {
199 | selectedSku = sku;
200 | GlideApp.with(context).load(selectedSku.getPictureUrl()).into(ivSkuLogo);
201 | List attributeList = selectedSku.getAttributes();
202 | StringBuilder builder = new StringBuilder();
203 | for (int i = 0; i < attributeList.size(); i++) {
204 | if (i != 0) {
205 | builder.append(" ");
206 | }
207 | SkuAttribute attribute = attributeList.get(i);
208 | builder.append("\"" + attribute.getValue() + "\"");
209 | }
210 | tvSkuInfo.setText("已选:" + builder.toString());
211 | setHaveSelect(true);
212 | //设置价格
213 | tvSkuSellingPrice.setText(String.format(priceFormat, sku.getPrice()));
214 | callback.onSelect(builder.toString());
215 | tvSkuQuantity.setText(String.format(stockQuantityFormat, selectedSku.getStockQuantity()));
216 | btnSubmit.setEnabled(true);
217 | if (sku.getStockQuantity() >= 1) {
218 | etSkuQuantityInput.setText("1");
219 | updateQuantityOperator(1);
220 | } else {
221 | etSkuQuantityInput.setText("0");
222 | updateQuantityOperator(0);
223 | }
224 | }
225 | });
226 | //添加
227 | btnSubmit.setOnClickListener(new View.OnClickListener() {
228 | @Override
229 | public void onClick(View v) {
230 | String quantity = etSkuQuantityInput.getText().toString();
231 | if (TextUtils.isEmpty(quantity)) {
232 | return;
233 | }
234 | int quantityInt = Integer.parseInt(quantity);
235 | if (quantityInt > 0 && quantityInt <= selectedSku.getStockQuantity()) {
236 | callback.onAdded(selectedSku, quantityInt);
237 | dismiss();
238 | } else {
239 | Toast.makeText(getContext(), "商品数量超出库存,请修改数量", Toast.LENGTH_SHORT).show();
240 | }
241 | }
242 | });
243 | }
244 |
245 | /**
246 | * 设置数据
247 | * @param product
248 | * @param callback
249 | */
250 | public void setData(final ProductData product, Callback callback) {
251 | this.product = product;
252 | this.skuList = product.getSkus();
253 | this.callback = callback;
254 | priceFormat = context.getString(R.string.comm_price_format);
255 | stockQuantityFormat = context.getString(R.string.sku_stock);
256 | updateSkuData();
257 | updateQuantityOperator(1);
258 | }
259 |
260 | /**
261 | * 更新数据
262 | */
263 | private void updateSkuData() {
264 | scrollSkuList.setSkuList(product.getSkus());
265 | //默认选择第一个
266 | Sku firstSku = product.getSkus().get(0);
267 | if (firstSku.getStockQuantity() > 0) {
268 | selectedSku = firstSku;
269 | // 选中第一个sku
270 | scrollSkuList.setSelectedSku(selectedSku);
271 | GlideApp.with(context).load(product.getPictureUrl()).into(ivSkuLogo);
272 | tvSkuSellingPrice.setText(String.format(priceFormat, selectedSku.getPrice()));
273 | tvSkuQuantity.setText(String.format(stockQuantityFormat, selectedSku.getStockQuantity()));
274 | btnSubmit.setEnabled(selectedSku.getStockQuantity() > 0);
275 | List attributeList = selectedSku.getAttributes();
276 | StringBuilder builder = new StringBuilder();
277 | int attributeListSize = attributeList.size();
278 | for (int i = 0; i < attributeListSize; i++) {
279 | if (i != 0) {
280 | builder.append(" ");
281 | }
282 | SkuAttribute attribute = attributeList.get(i);
283 | builder.append("\"" + attribute.getValue() + "\"");
284 | }
285 | tvSkuInfo.setText("已选:" + builder.toString());
286 | setHaveSelect(true);
287 | callback.onSelect(builder.toString());
288 | } else {
289 | GlideApp.with(context).load(product.getPictureUrl()).into(ivSkuLogo);
290 | tvSkuSellingPrice.setText(String.format(priceFormat, product.getMinPrice()));
291 | tvSkuQuantity.setText(String.format(stockQuantityFormat, product.getStockQuantity()));
292 | btnSubmit.setEnabled(false);
293 | tvSkuInfo.setText("请选择:" + skuList.get(0).getAttributes().get(0).getKey());
294 | setHaveSelect(false);
295 | callback.reUnSelect();
296 | }
297 | }
298 |
299 | /**
300 | * 获取已选中的
301 | *
302 | * @return
303 | */
304 | public boolean isHaveSelect;
305 |
306 | public boolean isHaveSelect() {
307 | return isHaveSelect;
308 | }
309 |
310 | public void setHaveSelect(boolean haveSelect) {
311 | isHaveSelect = haveSelect;
312 | }
313 |
314 | /**
315 | * 获取显示的内容
316 | *
317 | * @return
318 | */
319 | private String getSelected() {
320 | List skuAttributeList = scrollSkuList.getSelectedAttributeList();
321 | if (skuAttributeList != null) {
322 | //是否有属性是选中的
323 | setHaveSelect(false);
324 | StringBuilder stringBuilder = new StringBuilder();
325 | int skuAttributeListSize = skuAttributeList.size();
326 | for (int i = 0; i < skuAttributeListSize; i++) {
327 | SkuAttribute skuAttribute = skuAttributeList.get(i);
328 | if (i != 0) {
329 | stringBuilder.append(" ");
330 | }
331 | if (TextUtils.isEmpty(skuAttribute.getValue())) {
332 | stringBuilder.append(skuAttribute.getKey());
333 | } else {
334 | stringBuilder.append(skuAttribute.getValue());
335 | setHaveSelect(true);
336 | }
337 | }
338 | return stringBuilder.toString();
339 | } else
340 | return null;
341 | }
342 |
343 | private void updateQuantityOperator(int newQuantity) {
344 | if (selectedSku == null) {
345 | btnSkuQuantityMinus.setEnabled(false);
346 | btnSkuQuantityPlus.setEnabled(false);
347 | etSkuQuantityInput.setEnabled(false);
348 | } else {
349 | if (newQuantity <= 1) {
350 | btnSkuQuantityMinus.setEnabled(false);
351 | btnSkuQuantityPlus.setEnabled(true);
352 | } else if (newQuantity >= selectedSku.getStockQuantity()) {
353 | btnSkuQuantityMinus.setEnabled(true);
354 | btnSkuQuantityPlus.setEnabled(false);
355 | } else {
356 | btnSkuQuantityMinus.setEnabled(true);
357 | btnSkuQuantityPlus.setEnabled(true);
358 | }
359 | etSkuQuantityInput.setEnabled(true);
360 | }
361 |
362 | }
363 |
364 | @Override
365 | public void onAttachedToWindow() {
366 | super.onAttachedToWindow();
367 | // 解决键盘遮挡输入框问题
368 | Window window = getWindow();
369 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
370 | window.setGravity(Gravity.BOTTOM);
371 | window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
372 | window.getDecorView().setPadding(0, 0, 0, 0);
373 | }
374 |
375 | public interface Callback {
376 | //添加购物车
377 | void onAdded(Sku sku, int quantity);
378 |
379 | //已选
380 | void onSelect(String selected);
381 |
382 | //恢复默认未选
383 | void reUnSelect();
384 | }
385 |
386 | }
387 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/bean/ProductData.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.bean;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.Keep;
5 | import android.util.Log;
6 |
7 | import com.google.gson.Gson;
8 | import com.google.gson.reflect.TypeToken;
9 | import com.madreain.sku.R;
10 |
11 | import java.io.BufferedReader;
12 | import java.io.IOException;
13 | import java.io.InputStreamReader;
14 | import java.io.UnsupportedEncodingException;
15 | import java.util.List;
16 |
17 | /**
18 | * @author madreain
19 | * @date 2019-07-27.
20 | * module:
21 | * description:
22 | */
23 | @Keep
24 | public class ProductData {
25 |
26 | //商品id
27 | private long pid;
28 |
29 | //主图url
30 | private String pictureUrl;
31 |
32 | //最高价格
33 | private float maxPrice;
34 |
35 | //最低价格
36 | private float minPrice;
37 |
38 | //sku属性
39 | private List skus;
40 |
41 | //数量
42 | private int stockQuantity;
43 |
44 | public long getPid() {
45 | return pid;
46 | }
47 |
48 | public void setPid(long pid) {
49 | this.pid = pid;
50 | }
51 |
52 | public int getStockQuantity() {
53 | return stockQuantity;
54 | }
55 |
56 | public void setStockQuantity(int stockQuantity) {
57 | this.stockQuantity = stockQuantity;
58 | }
59 |
60 | public static ProductData get(Context context) {
61 | String json = readTextFromSDcard(context);
62 | return new Gson().fromJson(json, new TypeToken() {
63 | }.getType());
64 | }
65 |
66 | private static String readTextFromSDcard(Context context) {
67 | String resultString = null;
68 | InputStreamReader inputStreamReader;
69 | try {
70 | inputStreamReader = new InputStreamReader(context.getAssets().open("Product.json"), "UTF-8");
71 | BufferedReader bufferedReader = new BufferedReader(
72 | inputStreamReader);
73 | String line;
74 | StringBuilder stringBuilder = new StringBuilder();
75 | while ((line = bufferedReader.readLine()) != null) {
76 | stringBuilder.append(line);
77 | }
78 | inputStreamReader.close();
79 | bufferedReader.close();
80 | resultString = stringBuilder.toString();
81 | } catch (UnsupportedEncodingException e) {
82 | e.printStackTrace();
83 | } catch (IOException e) {
84 | e.printStackTrace();
85 | }
86 | return resultString;
87 | }
88 |
89 | public String getPictureUrl() {
90 | return pictureUrl;
91 | }
92 |
93 | public void setPictureUrl(String pictureUrl) {
94 | this.pictureUrl = pictureUrl;
95 | }
96 |
97 | public float getMaxPrice() {
98 | return maxPrice;
99 | }
100 |
101 | public void setMaxPrice(float maxPrice) {
102 | this.maxPrice = maxPrice;
103 | }
104 |
105 | public float getMinPrice() {
106 | return minPrice;
107 | }
108 |
109 | public void setMinPrice(float minPrice) {
110 | this.minPrice = minPrice;
111 | }
112 |
113 | public List getSkus() {
114 | return skus;
115 | }
116 |
117 | public void setSkus(List skus) {
118 | this.skus = skus;
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/bean/Sku.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.bean;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 | import android.support.annotation.Keep;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * @author madreain
11 | * @date 2019-07-27.
12 | * module:
13 | * description:
14 | */
15 | @Keep
16 | public class Sku implements Parcelable {
17 |
18 | private long sid;
19 |
20 | private float price;
21 |
22 | private String pictureUrl;
23 |
24 | private List attributes;
25 |
26 | private int stockQuantity;
27 |
28 | public long getSid() {
29 | return sid;
30 | }
31 |
32 | public void setSid(long sid) {
33 | this.sid = sid;
34 | }
35 |
36 | public int getStockQuantity() {
37 | return stockQuantity;
38 | }
39 |
40 | public void setStockQuantity(int stockQuantity) {
41 | this.stockQuantity = stockQuantity;
42 | }
43 |
44 | public float getPrice() {
45 | return price;
46 | }
47 |
48 | public void setPrice(float price) {
49 | this.price = price;
50 | }
51 |
52 | public String getPictureUrl() {
53 | return pictureUrl;
54 | }
55 |
56 | public void setPictureUrl(String pictureUrl) {
57 | this.pictureUrl = pictureUrl;
58 | }
59 |
60 | public List getAttributes() {
61 | return attributes;
62 | }
63 |
64 | public void setAttributes(List attributes) {
65 | this.attributes = attributes;
66 | }
67 |
68 | @Override
69 | public String toString() {
70 | return "Sku{" +
71 | "sid=" + sid +
72 | ", price=" + price +
73 | ", pictureUrl='" + pictureUrl + '\'' +
74 | ", attributes=" + attributes +
75 | ", stockQuantity=" + stockQuantity +
76 | '}';
77 | }
78 |
79 | public Sku() {
80 |
81 | }
82 |
83 | /**
84 | * 描述
85 | * @return
86 | */
87 | @Override
88 | public int describeContents() {
89 | return 0;
90 | }
91 |
92 | /**
93 | * 反序列化
94 | * @param dest
95 | * @param flags
96 | */
97 | @Override
98 | public void writeToParcel(Parcel dest, int flags) {
99 | dest.writeLong(this.sid);
100 | dest.writeFloat(this.price);
101 | dest.writeString(this.pictureUrl);
102 | dest.writeTypedList(this.attributes);
103 | }
104 |
105 | /**
106 | * 序列化
107 | * @param in
108 | */
109 | protected Sku(Parcel in) {
110 | this.sid = in.readLong();
111 | this.price = in.readFloat();
112 | this.pictureUrl = in.readString();
113 | this.attributes = in.createTypedArrayList(SkuAttribute.CREATOR);
114 | }
115 |
116 | public static final Creator CREATOR = new Creator() {
117 | @Override
118 | public Sku createFromParcel(Parcel source) {
119 | return new Sku(source);
120 | }
121 |
122 | @Override
123 | public Sku[] newArray(int size) {
124 | return new Sku[size];
125 | }
126 | };
127 |
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/bean/SkuAttribute.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.bean;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 | import android.support.annotation.Keep;
6 |
7 | /**
8 | * @author madreain
9 | * @date 2019-07-27.
10 | * module:
11 | * description:
12 | */
13 | @Keep
14 | public class SkuAttribute implements Parcelable {
15 |
16 | private String key;
17 |
18 | private String value;
19 |
20 | public SkuAttribute() {
21 | }
22 |
23 | public SkuAttribute(String key, String value) {
24 | this.key = key;
25 | this.value = value;
26 | }
27 |
28 | public String getValue() {
29 | return value;
30 | }
31 |
32 | public void setValue(String value) {
33 | this.value = value;
34 | }
35 |
36 | public String getKey() {
37 | return key;
38 | }
39 |
40 | public void setKey(String key) {
41 | this.key = key;
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return "SkuAttribute{" +
47 | "key='" + key + '\'' +
48 | ", value='" + value + '\'' +
49 | '}';
50 | }
51 |
52 | /**
53 | * 描述
54 | * @return
55 | */
56 | @Override
57 | public int describeContents() {
58 | return 0;
59 | }
60 |
61 | /**
62 | * 反序列化
63 | * @param dest
64 | * @param flags
65 | */
66 | @Override
67 | public void writeToParcel(Parcel dest, int flags) {
68 | dest.writeString(this.key);
69 | dest.writeString(this.value);
70 | }
71 |
72 | /**
73 | * 序列化
74 | * @param in
75 | */
76 | protected SkuAttribute(Parcel in) {
77 | this.key = in.readString();
78 | this.value = in.readString();
79 | }
80 |
81 | public static final Creator CREATOR = new Creator() {
82 | @Override
83 | public SkuAttribute createFromParcel(Parcel source) {
84 | return new SkuAttribute(source);
85 | }
86 |
87 | @Override
88 | public SkuAttribute[] newArray(int size) {
89 | return new SkuAttribute[size];
90 | }
91 | };
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/utils/DisplayUtil.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.utils;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * @author madreain
7 | * @date 2019-07-27.
8 | * module:
9 | * description:
10 | */
11 | public class DisplayUtil {
12 |
13 | public static int dp2px(Context context, float dp) {
14 | final float scale = context.getResources().getDisplayMetrics().density;
15 | return (int) (dp * scale + 0.5f);
16 | }
17 |
18 | public static int px2dp(Context context, float pxValue) {
19 | final float scale = context.getResources().getDisplayMetrics().density;
20 | return (int) (pxValue / scale + 0.5f);
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/view/FlowLayout.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 | import android.util.TypedValue;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 |
10 |
11 | import com.madreain.sku.R;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | /**
17 | * @author madreain
18 | * @date 2019-07-27.
19 | * module:
20 | * description:
21 | */
22 | public class FlowLayout extends ViewGroup {
23 | private static final String LOG_TAG = FlowLayout.class.getSimpleName();
24 |
25 | /**
26 | * Special value for the child view spacing.
27 | * SPACING_AUTO means that the actual spacing is calculated according to the size of the
28 | * container and the number of the child views, so that the child views are placed evenly in
29 | * the container.
30 | */
31 | public static final int SPACING_AUTO = -65536;
32 |
33 | /**
34 | * Special value for the horizontal spacing of the child views in the last row
35 | * SPACING_ALIGN means that the horizontal spacing of the child views in the last row keeps
36 | * the same with the spacing used in the row above. If there is only one row, this value is
37 | * ignored and the spacing will be calculated according to childSpacing.
38 | */
39 | public static final int SPACING_ALIGN = -65537;
40 |
41 | private static final int SPACING_UNDEFINED = -65538;
42 |
43 | private static final boolean DEFAULT_FLOW = true;
44 | private static final int DEFAULT_CHILD_SPACING = 0;
45 | private static final int DEFAULT_CHILD_SPACING_FOR_LAST_ROW = SPACING_UNDEFINED;
46 | private static final float DEFAULT_ROW_SPACING = 0;
47 | private static final boolean DEFAULT_RTL = false;
48 | private static final int DEFAULT_MAX_ROWS = Integer.MAX_VALUE;
49 |
50 | private boolean mFlow = DEFAULT_FLOW;
51 | private int mChildSpacing = DEFAULT_CHILD_SPACING;
52 | private int mChildSpacingForLastRow = DEFAULT_CHILD_SPACING_FOR_LAST_ROW;
53 | private float mRowSpacing = DEFAULT_ROW_SPACING;
54 | private float mAdjustedRowSpacing = DEFAULT_ROW_SPACING;
55 | private boolean mRtl = DEFAULT_RTL;
56 | private int mMaxRows = DEFAULT_MAX_ROWS;
57 |
58 | private List mHorizontalSpacingForRow = new ArrayList<>();
59 | private List mHeightForRow = new ArrayList<>();
60 | private List mChildNumForRow = new ArrayList<>();
61 |
62 | public FlowLayout(Context context) {
63 | this(context, null);
64 | }
65 |
66 | public FlowLayout(Context context, AttributeSet attrs) {
67 | super(context, attrs);
68 |
69 | TypedArray a = context.getTheme().obtainStyledAttributes(
70 | attrs, R.styleable.FlowLayout, 0, 0);
71 | try {
72 | mFlow = a.getBoolean(R.styleable.FlowLayout_flow, DEFAULT_FLOW);
73 | try {
74 | mChildSpacing = a.getInt(R.styleable.FlowLayout_childSpacing, DEFAULT_CHILD_SPACING);
75 | } catch (NumberFormatException e) {
76 | mChildSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_childSpacing, (int) dpToPx(DEFAULT_CHILD_SPACING));
77 | }
78 | try {
79 | mChildSpacingForLastRow = a.getInt(R.styleable.FlowLayout_childSpacingForLastRow, SPACING_UNDEFINED);
80 | } catch (NumberFormatException e) {
81 | mChildSpacingForLastRow = a.getDimensionPixelSize(R.styleable.FlowLayout_childSpacingForLastRow, (int) dpToPx(DEFAULT_CHILD_SPACING));
82 | }
83 | try {
84 | mRowSpacing = a.getInt(R.styleable.FlowLayout_rowSpacing, 0);
85 | } catch (NumberFormatException e) {
86 | mRowSpacing = a.getDimension(R.styleable.FlowLayout_rowSpacing, dpToPx(DEFAULT_ROW_SPACING));
87 | }
88 | mMaxRows = a.getInt(R.styleable.FlowLayout_maxRows, DEFAULT_MAX_ROWS);
89 | mRtl = a.getBoolean(R.styleable.FlowLayout_rtl, DEFAULT_RTL);
90 | } finally {
91 | a.recycle();
92 | }
93 | }
94 |
95 | @Override
96 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
97 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
98 |
99 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
100 | int widthMode = MeasureSpec.getMode(widthMeasureSpec);
101 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
102 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
103 |
104 | mHorizontalSpacingForRow.clear();
105 | mChildNumForRow.clear();
106 | mHeightForRow.clear();
107 |
108 | int measuredHeight = 0, measuredWidth = 0, childCount = getChildCount();
109 | int rowWidth = 0, maxChildHeightInRow = 0, childNumInRow = 0;
110 | int rowSize = widthSize - getPaddingLeft() - getPaddingRight();
111 | boolean allowFlow = widthMode != MeasureSpec.UNSPECIFIED && mFlow;
112 | int childSpacing = mChildSpacing == SPACING_AUTO && widthMode == MeasureSpec.UNSPECIFIED
113 | ? 0 : mChildSpacing;
114 | float tmpSpacing = childSpacing == SPACING_AUTO ? 0 : childSpacing;
115 |
116 | for (int i = 0; i < childCount; i++) {
117 | View child = getChildAt(i);
118 | if (child.getVisibility() == GONE) {
119 | continue;
120 | }
121 |
122 | LayoutParams childParams = child.getLayoutParams();
123 | int horizontalMargin = 0, verticalMargin = 0;
124 | if (childParams instanceof MarginLayoutParams) {
125 | measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, measuredHeight);
126 | MarginLayoutParams marginParams = (MarginLayoutParams) childParams;
127 | horizontalMargin = marginParams.leftMargin + marginParams.rightMargin;
128 | verticalMargin = marginParams.topMargin + marginParams.bottomMargin;
129 | } else {
130 | measureChild(child, widthMeasureSpec, heightMeasureSpec);
131 | }
132 |
133 | int childWidth = child.getMeasuredWidth() + horizontalMargin;
134 | int childHeight = child.getMeasuredHeight() + verticalMargin;
135 | if (allowFlow && rowWidth + childWidth > rowSize) { // Need flow to next row
136 | // Save parameters for current row
137 | mHorizontalSpacingForRow.add(
138 | getSpacingForRow(childSpacing, rowSize, rowWidth, childNumInRow));
139 | mChildNumForRow.add(childNumInRow);
140 | mHeightForRow.add(maxChildHeightInRow);
141 | if (mHorizontalSpacingForRow.size() <= mMaxRows) {
142 | measuredHeight += maxChildHeightInRow;
143 | }
144 | measuredWidth = Math.max(measuredWidth, rowWidth);
145 |
146 | // Place the child view to next row
147 | childNumInRow = 1;
148 | rowWidth = childWidth + (int) tmpSpacing;
149 | maxChildHeightInRow = childHeight;
150 | } else {
151 | childNumInRow++;
152 | rowWidth += childWidth + tmpSpacing;
153 | maxChildHeightInRow = Math.max(maxChildHeightInRow, childHeight);
154 | }
155 | }
156 |
157 | // Measure remaining child views in the last row
158 | if (mChildSpacingForLastRow == SPACING_ALIGN) {
159 | // For SPACING_ALIGN, use the same spacing from the row above if there is more than one
160 | // row.
161 | if (mHorizontalSpacingForRow.size() >= 1) {
162 | mHorizontalSpacingForRow.add(
163 | mHorizontalSpacingForRow.get(mHorizontalSpacingForRow.size() - 1));
164 | } else {
165 | mHorizontalSpacingForRow.add(
166 | getSpacingForRow(childSpacing, rowSize, rowWidth, childNumInRow));
167 | }
168 | } else if (mChildSpacingForLastRow != SPACING_UNDEFINED) {
169 | // For SPACING_AUTO and specific DP values, apply them to the spacing strategy.
170 | mHorizontalSpacingForRow.add(
171 | getSpacingForRow(mChildSpacingForLastRow, rowSize, rowWidth, childNumInRow));
172 | } else {
173 | // For SPACING_UNDEFINED, apply childSpacing to the spacing strategy for the last row.
174 | mHorizontalSpacingForRow.add(
175 | getSpacingForRow(childSpacing, rowSize, rowWidth, childNumInRow));
176 | }
177 |
178 | mChildNumForRow.add(childNumInRow);
179 | mHeightForRow.add(maxChildHeightInRow);
180 | if (mHorizontalSpacingForRow.size() <= mMaxRows) {
181 | measuredHeight += maxChildHeightInRow;
182 | }
183 | measuredWidth = Math.max(measuredWidth, rowWidth);
184 |
185 | if (childSpacing == SPACING_AUTO) {
186 | measuredWidth = widthSize;
187 | } else if (widthMode == MeasureSpec.UNSPECIFIED) {
188 | measuredWidth = measuredWidth + getPaddingLeft() + getPaddingRight();
189 | } else {
190 | measuredWidth = Math.min(measuredWidth + getPaddingLeft() + getPaddingRight(), widthSize);
191 | }
192 |
193 | measuredHeight += getPaddingTop() + getPaddingBottom();
194 | int rowNum = Math.min(mHorizontalSpacingForRow.size(), mMaxRows);
195 | float rowSpacing = mRowSpacing == SPACING_AUTO && heightMode == MeasureSpec.UNSPECIFIED
196 | ? 0 : mRowSpacing;
197 | if (rowSpacing == SPACING_AUTO) {
198 | if (rowNum > 1) {
199 | mAdjustedRowSpacing = (heightSize - measuredHeight) / (rowNum - 1);
200 | } else {
201 | mAdjustedRowSpacing = 0;
202 | }
203 | measuredHeight = heightSize;
204 | } else {
205 | mAdjustedRowSpacing = rowSpacing;
206 | if (rowNum > 1) {
207 | measuredHeight = heightMode == MeasureSpec.UNSPECIFIED
208 | ? ((int) (measuredHeight + mAdjustedRowSpacing * (rowNum - 1)))
209 | : (Math.min((int) (measuredHeight + mAdjustedRowSpacing * (rowNum - 1)),
210 | heightSize));
211 | }
212 | }
213 |
214 | measuredWidth = widthMode == MeasureSpec.EXACTLY ? widthSize : measuredWidth;
215 | measuredHeight = heightMode == MeasureSpec.EXACTLY ? heightSize : measuredHeight;
216 | setMeasuredDimension(measuredWidth, measuredHeight);
217 | }
218 |
219 | @Override
220 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
221 | int paddingLeft = getPaddingLeft();
222 | int paddingRight = getPaddingRight();
223 | int paddingTop = getPaddingTop();
224 | int x = mRtl ? (getWidth() - paddingRight) : paddingLeft;
225 | int y = paddingTop;
226 |
227 | int rowCount = mChildNumForRow.size(), childIdx = 0;
228 | for (int row = 0; row < rowCount; row++) {
229 | int childNum = mChildNumForRow.get(row);
230 | int rowHeight = mHeightForRow.get(row);
231 | float spacing = mHorizontalSpacingForRow.get(row);
232 | for (int i = 0; i < childNum && childIdx < getChildCount(); ) {
233 | View child = getChildAt(childIdx++);
234 | if (child.getVisibility() == GONE) {
235 | continue;
236 | } else {
237 | i++;
238 | }
239 |
240 | LayoutParams childParams = child.getLayoutParams();
241 | int marginLeft = 0, marginTop = 0, marginRight = 0;
242 | if (childParams instanceof MarginLayoutParams) {
243 | MarginLayoutParams marginParams = (MarginLayoutParams) childParams;
244 | marginLeft = marginParams.leftMargin;
245 | marginRight = marginParams.rightMargin;
246 | marginTop = marginParams.topMargin;
247 | }
248 |
249 | int childWidth = child.getMeasuredWidth();
250 | int childHeight = child.getMeasuredHeight();
251 | if (mRtl) {
252 | child.layout(x - marginRight - childWidth, y + marginTop,
253 | x - marginRight, y + marginTop + childHeight);
254 | x -= childWidth + spacing + marginLeft + marginRight;
255 | } else {
256 | child.layout(x + marginLeft, y + marginTop,
257 | x + marginLeft + childWidth, y + marginTop + childHeight);
258 | x += childWidth + spacing + marginLeft + marginRight;
259 | }
260 | }
261 | x = mRtl ? (getWidth() - paddingRight) : paddingLeft;
262 | y += rowHeight + mAdjustedRowSpacing;
263 | }
264 | }
265 |
266 | @Override
267 | protected LayoutParams generateLayoutParams(LayoutParams p) {
268 | return new MarginLayoutParams(p);
269 | }
270 |
271 | @Override
272 | public LayoutParams generateLayoutParams(AttributeSet attrs) {
273 | return new MarginLayoutParams(getContext(), attrs);
274 | }
275 |
276 | /**
277 | * Returns whether to allow child views flow to next row when there is no enough space.
278 | *
279 | * @return Whether to flow child views to next row when there is no enough space.
280 | */
281 | public boolean isFlow() {
282 | return mFlow;
283 | }
284 |
285 | /**
286 | * Sets whether to allow child views flow to next row when there is no enough space.
287 | *
288 | * @param flow true to allow flow. false to restrict all child views in one row.
289 | */
290 | public void setFlow(boolean flow) {
291 | mFlow = flow;
292 | requestLayout();
293 | }
294 |
295 | /**
296 | * Returns the horizontal spacing between child views.
297 | *
298 | * @return The spacing, either {@link FlowLayout#SPACING_AUTO}, or a fixed size in pixels.
299 | */
300 | public int getChildSpacing() {
301 | return mChildSpacing;
302 | }
303 |
304 | /**
305 | * Sets the horizontal spacing between child views.
306 | *
307 | * @param childSpacing The spacing, either {@link FlowLayout#SPACING_AUTO}, or a fixed size in
308 | * pixels.
309 | */
310 | public void setChildSpacing(int childSpacing) {
311 | mChildSpacing = childSpacing;
312 | requestLayout();
313 | }
314 |
315 | /**
316 | * Returns the horizontal spacing between child views of the last row.
317 | *
318 | * @return The spacing, either {@link FlowLayout#SPACING_AUTO},
319 | * {@link FlowLayout#SPACING_ALIGN}, or a fixed size in pixels
320 | */
321 | public int getChildSpacingForLastRow() {
322 | return mChildSpacingForLastRow;
323 | }
324 |
325 | /**
326 | * Sets the horizontal spacing between child views of the last row.
327 | *
328 | * @param childSpacingForLastRow The spacing, either {@link FlowLayout#SPACING_AUTO},
329 | * {@link FlowLayout#SPACING_ALIGN}, or a fixed size in pixels
330 | */
331 | public void setChildSpacingForLastRow(int childSpacingForLastRow) {
332 | mChildSpacingForLastRow = childSpacingForLastRow;
333 | requestLayout();
334 | }
335 |
336 | /**
337 | * Returns the vertical spacing between rows.
338 | *
339 | * @return The spacing, either {@link FlowLayout#SPACING_AUTO}, or a fixed size in pixels.
340 | */
341 | public float getRowSpacing() {
342 | return mRowSpacing;
343 | }
344 |
345 | /**
346 | * Sets the vertical spacing between rows in pixels. Use SPACING_AUTO to evenly place all rows
347 | * in vertical.
348 | *
349 | * @param rowSpacing The spacing, either {@link FlowLayout#SPACING_AUTO}, or a fixed size in
350 | * pixels.
351 | */
352 | public void setRowSpacing(float rowSpacing) {
353 | mRowSpacing = rowSpacing;
354 | requestLayout();
355 | }
356 |
357 | /**
358 | * Returns the maximum number of rows of the FlowLayout.
359 | *
360 | * @return The maximum number of rows.
361 | */
362 | public int getMaxRows() {
363 | return mMaxRows;
364 | }
365 |
366 | /**
367 | * Sets the number of row of the FlowLayout to be at most maxRows size.
368 | *
369 | * @param maxRows The maximum number of rows.
370 | */
371 | public void setMaxRows(int maxRows) {
372 | mMaxRows = maxRows;
373 | requestLayout();
374 | }
375 |
376 | private float getSpacingForRow(int spacingAttribute, int rowSize, int usedSize, int childNum) {
377 | float spacing;
378 | if (spacingAttribute == SPACING_AUTO) {
379 | if (childNum > 1) {
380 | spacing = (rowSize - usedSize) / (childNum - 1);
381 | } else {
382 | spacing = 0;
383 | }
384 | } else {
385 | spacing = spacingAttribute;
386 | }
387 | return spacing;
388 | }
389 |
390 | private float dpToPx(float dp) {
391 | return TypedValue.applyDimension(
392 | TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
393 | }
394 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/view/OnSkuListener.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.view;
2 |
3 |
4 | import com.madreain.sku.bean.Sku;
5 | import com.madreain.sku.bean.SkuAttribute;
6 |
7 | /**
8 | * @author madreain
9 | * @date 2019-07-27.
10 | * module:
11 | * description:
12 | */
13 | public interface OnSkuListener {
14 | /**
15 | * 属性取消选中
16 | *
17 | * @param unselectedAttribute
18 | */
19 | void onUnselected(SkuAttribute unselectedAttribute);
20 |
21 | /**
22 | * 属性选中
23 | *
24 | * @param selectAttribute
25 | */
26 | void onSelect(SkuAttribute selectAttribute);
27 |
28 | /**
29 | * sku选中
30 | *
31 | * @param sku
32 | */
33 | void onSkuSelected(Sku sku);
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/view/SkuItemLayout.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.view;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.Nullable;
5 | import android.text.TextPaint;
6 | import android.util.AttributeSet;
7 | import android.util.TypedValue;
8 | import android.view.View;
9 | import android.widget.LinearLayout;
10 | import android.widget.TextView;
11 |
12 |
13 | import com.madreain.sku.R;
14 | import com.madreain.sku.bean.SkuAttribute;
15 | import com.madreain.sku.utils.DisplayUtil;
16 |
17 | import java.util.List;
18 |
19 | /**
20 | * @author madreain
21 | * @date 2019-07-27.
22 | * module:
23 | * description:
24 | */
25 |
26 | public class SkuItemLayout extends LinearLayout {
27 | private TextView attributeNameTv;
28 | private FlowLayout attributeValueLayout;
29 | private OnSkuItemSelectListener listener;
30 |
31 | public SkuItemLayout(Context context) {
32 | super(context);
33 | init(context);
34 | }
35 |
36 | public SkuItemLayout(Context context, @Nullable AttributeSet attrs) {
37 | super(context, attrs);
38 | init(context);
39 | }
40 |
41 | public SkuItemLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
42 | super(context, attrs, defStyleAttr);
43 | init(context);
44 | }
45 |
46 | private void init(Context context) {
47 | setOrientation(VERTICAL);
48 | //类目的展示
49 | attributeNameTv = new TextView(context);
50 | attributeNameTv.setTextColor(context.getResources().getColor(R.color.m2F2F39));
51 | attributeNameTv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
52 | attributeNameTv.setIncludeFontPadding(false);
53 | //字体加粗
54 | TextPaint tp = attributeNameTv.getPaint();
55 | tp.setFakeBoldText(true);
56 | LayoutParams attributeNameParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
57 | attributeNameParams.leftMargin = DisplayUtil.dp2px(context, 16);
58 | attributeNameParams.topMargin = DisplayUtil.dp2px(context, 24);
59 | attributeNameTv.setLayoutParams(attributeNameParams);
60 | addView(attributeNameTv);
61 | //sku属性展示
62 | attributeValueLayout = new FlowLayout(context);
63 | attributeValueLayout.setMinimumHeight(DisplayUtil.dp2px(context, 25));
64 | attributeValueLayout.setChildSpacing(DisplayUtil.dp2px(context, 15));
65 | attributeValueLayout.setRowSpacing(DisplayUtil.dp2px(context, 15));
66 | LayoutParams attributeValueParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
67 | attributeValueParams.leftMargin = DisplayUtil.dp2px(context, 15);
68 | attributeValueParams.rightMargin = DisplayUtil.dp2px(context, 15);
69 | attributeValueParams.topMargin = DisplayUtil.dp2px(context, 15);
70 | attributeValueParams.bottomMargin = DisplayUtil.dp2px(context, 10);
71 | attributeValueLayout.setLayoutParams(attributeValueParams);
72 | addView(attributeValueLayout);
73 | //横线的展示
74 | View line = new View(context);
75 | line.setBackgroundResource(R.color.m202F2F39);
76 | LayoutParams lineParams = new LayoutParams(LayoutParams.MATCH_PARENT, 1);
77 | lineParams.leftMargin = DisplayUtil.dp2px(context, 15);
78 | lineParams.rightMargin = DisplayUtil.dp2px(context, 15);
79 | lineParams.topMargin = DisplayUtil.dp2px(context, 5);
80 | line.setLayoutParams(lineParams);
81 | addView(line);
82 | }
83 |
84 | /**
85 | * sku属性展示
86 | * @param position
87 | * @param attributeName
88 | * @param attributeValueList
89 | */
90 | public void buildItemLayout(int position, String attributeName, List attributeValueList) {
91 | attributeNameTv.setText(attributeName);
92 | attributeValueLayout.removeAllViewsInLayout();
93 | for (String attributeValue : attributeValueList) {
94 | SkuItemView itemView = new SkuItemView(getContext());
95 | itemView.setAttributeValue(attributeValue);
96 | itemView.setOnClickListener(new ItemClickListener(position, itemView));
97 | itemView.setLayoutParams(new FlowLayout.LayoutParams(
98 | FlowLayout.LayoutParams.WRAP_CONTENT,
99 | DisplayUtil.dp2px(getContext(), 36)));
100 | attributeValueLayout.addView(itemView);
101 | }
102 | }
103 |
104 | /**
105 | * 清空是否可点击,选中状态
106 | */
107 | public void clearItemViewStatus() {
108 | int attributeValueLayoutChildCount = attributeValueLayout.getChildCount();
109 | for (int i = 0; i < attributeValueLayoutChildCount; i++) {
110 | SkuItemView itemView = (SkuItemView) attributeValueLayout.getChildAt(i);
111 | itemView.setSelected(false);
112 | itemView.setEnabled(false);
113 | }
114 | }
115 |
116 | /**
117 | * 设置指定属性为可点击状态
118 | *
119 | * @param attributeValue
120 | */
121 | public void optionItemViewEnableStatus(String attributeValue) {
122 | int attributeValueLayoutChildCount = attributeValueLayout.getChildCount();
123 | for (int i = 0; i < attributeValueLayoutChildCount; i++) {
124 | SkuItemView itemView = (SkuItemView) attributeValueLayout.getChildAt(i);
125 | if (attributeValue.equals(itemView.getAttributeValue())) {
126 | itemView.setEnabled(true);
127 | }
128 | }
129 | }
130 |
131 | /**
132 | * 设置指定属性为选中状态
133 | *
134 | * @param selectValue
135 | */
136 | public void optionItemViewSelectStatus(SkuAttribute selectValue) {
137 | int attributeValueLayoutChildCount = attributeValueLayout.getChildCount();
138 | for (int i = 0; i < attributeValueLayoutChildCount; i++) {
139 | SkuItemView itemView = (SkuItemView) attributeValueLayout.getChildAt(i);
140 | if (selectValue.getValue().equals(itemView.getAttributeValue())) {
141 | itemView.setEnabled(true);
142 | itemView.setSelected(true);
143 | }
144 | }
145 | }
146 |
147 | /**
148 | * 当前属性集合是否有选中项
149 | *
150 | * @return
151 | */
152 | public boolean isSelected() {
153 | int attributeValueLayoutChildCount = attributeValueLayout.getChildCount();
154 | for (int i = 0; i < attributeValueLayoutChildCount; i++) {
155 | SkuItemView itemView = (SkuItemView) attributeValueLayout.getChildAt(i);
156 | if (itemView.isSelected()) {
157 | return true;
158 | }
159 | }
160 | return false;
161 | }
162 |
163 | /**
164 | * 获取属性名称
165 | *
166 | * @return
167 | */
168 | public String getAttributeName() {
169 | return attributeNameTv.getText().toString();
170 | }
171 |
172 | /**
173 | * sku点击
174 | * @param position
175 | * @param view
176 | */
177 | private void onSkuItemClicked(int position, SkuItemView view) {
178 | boolean selected = !view.isSelected();
179 | SkuAttribute attribute = new SkuAttribute();
180 | attribute.setKey(attributeNameTv.getText().toString());
181 | attribute.setValue(view.getAttributeValue());
182 | listener.onSelect(position, selected, attribute);
183 | }
184 |
185 | /**
186 | * 点击事件
187 | */
188 | private class ItemClickListener implements OnClickListener {
189 | private int position;
190 | private SkuItemView view;
191 |
192 | ItemClickListener(int position, SkuItemView view) {
193 | this.position = position;
194 | this.view = view;
195 | }
196 |
197 | @Override
198 | public void onClick(View v) {
199 | onSkuItemClicked(position, view);
200 | }
201 | }
202 |
203 | public void setOnSkuItemSelectListener(OnSkuItemSelectListener listener) {
204 | this.listener = listener;
205 | }
206 |
207 | interface OnSkuItemSelectListener {
208 | void onSelect(int position, boolean select, SkuAttribute attribute);
209 | }
210 |
211 | }
212 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/view/SkuItemView.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.view;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.support.annotation.Nullable;
6 | import android.util.AttributeSet;
7 | import android.util.TypedValue;
8 | import android.view.Gravity;
9 | import android.widget.TextView;
10 |
11 | import com.madreain.sku.R;
12 | import com.madreain.sku.utils.DisplayUtil;
13 |
14 |
15 | /**
16 | * @author madreain
17 | * @date 2019-07-27.
18 | * module:
19 | * description:
20 | */
21 |
22 | @SuppressLint("AppCompatCustomView")
23 | public class SkuItemView extends TextView {
24 | private String attributeValue;
25 |
26 | public SkuItemView(Context context) {
27 | super(context);
28 | init(context);
29 | }
30 |
31 | public SkuItemView(Context context, @Nullable AttributeSet attrs) {
32 | super(context, attrs);
33 | init(context);
34 | }
35 |
36 | public SkuItemView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
37 | super(context, attrs, defStyleAttr);
38 | init(context);
39 | }
40 |
41 | private void init(Context context) {
42 | setBackgroundResource(R.drawable.sku_item_selector);
43 | setTextColor(getResources().getColorStateList(R.color.sku_item_text_selector));
44 | setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
45 | setSingleLine();
46 | setGravity(Gravity.CENTER);
47 | setPadding(DisplayUtil.dp2px(context, 10), 0, DisplayUtil.dp2px(context, 10), 0);
48 | setMinWidth(DisplayUtil.dp2px(context, 45));
49 | setMaxWidth(DisplayUtil.dp2px(context, 200));
50 | }
51 |
52 | public String getAttributeValue() {
53 | return attributeValue;
54 | }
55 |
56 | public void setAttributeValue(String attributeValue) {
57 | this.attributeValue = attributeValue;
58 | setText(attributeValue);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/view/SkuMaxHeightScrollView.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 | import android.widget.ScrollView;
8 |
9 | import com.madreain.sku.R;
10 | import com.madreain.sku.utils.DisplayUtil;
11 |
12 | /**
13 | * @author madreain
14 | * @date 2019-07-27.
15 | * module:
16 | * description:解决Sku过多时,选择界面铺满全屏的问题
17 | */
18 | public class SkuMaxHeightScrollView extends ScrollView {
19 |
20 | private int maxHeight;
21 | private int minHeight;
22 |
23 | public SkuMaxHeightScrollView(Context context) {
24 | this(context, null);
25 | }
26 |
27 | public SkuMaxHeightScrollView(Context context, AttributeSet attrs) {
28 | super(context, attrs);
29 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SkuMaxHeightScrollView, 0, 0);
30 | try {
31 | maxHeight = typedArray.getInt(R.styleable.SkuMaxHeightScrollView_maxSkuHeight, 220);
32 | minHeight = typedArray.getInt(R.styleable.SkuMaxHeightScrollView_minSkuHeight, 75);
33 | } finally {
34 | typedArray.recycle();
35 | }
36 | }
37 |
38 | @Override
39 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
40 | int width = MeasureSpec.getSize(widthMeasureSpec);
41 | int height = 0;
42 | for (int i = 0; i < getChildCount(); i++) {
43 | View child = getChildAt(i);
44 | child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
45 | int h = child.getMeasuredHeight();
46 | if (h > height)
47 | height = h;
48 | }
49 | float heightDp = DisplayUtil.px2dp(getContext(), height);
50 | int skumaxHeight = DisplayUtil.dp2px(getContext(), maxHeight);
51 | int skuminHeight = DisplayUtil.dp2px(getContext(), minHeight);
52 | if (heightDp > skumaxHeight) {
53 | setMeasuredDimension(width, skumaxHeight);
54 | } else if (heightDp < skuminHeight) {
55 | setMeasuredDimension(width, skuminHeight);
56 | } else {
57 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/madreain/sku/view/SkuSelectScrollView.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku.view;
2 |
3 | import android.content.Context;
4 | import android.text.TextUtils;
5 | import android.util.AttributeSet;
6 | import android.widget.FrameLayout;
7 | import android.widget.LinearLayout;
8 |
9 |
10 | import com.madreain.sku.bean.Sku;
11 | import com.madreain.sku.bean.SkuAttribute;
12 |
13 | import java.util.Iterator;
14 | import java.util.LinkedHashMap;
15 | import java.util.LinkedList;
16 | import java.util.List;
17 | import java.util.Map;
18 |
19 | /**
20 | * @author madreain
21 | * @date 2019-07-27.
22 | * module:
23 | * description:
24 | */
25 | public class SkuSelectScrollView extends SkuMaxHeightScrollView implements SkuItemLayout.OnSkuItemSelectListener {
26 | private LinearLayout skuContainerLayout;
27 | private List skuList;
28 | private List selectedAttributeList; // 存放当前属性选中信息
29 | private OnSkuListener listener; // sku选中状态回调接口
30 |
31 | public SkuSelectScrollView(Context context) {
32 | super(context);
33 | init(context, null);
34 | }
35 |
36 | public SkuSelectScrollView(Context context, AttributeSet attrs) {
37 | super(context, attrs);
38 | init(context, attrs);
39 | }
40 |
41 | private void init(Context context, AttributeSet attrs) {
42 | setFillViewport(true);
43 | setOverScrollMode(OVER_SCROLL_NEVER);
44 | skuContainerLayout = new LinearLayout(context, attrs);
45 | skuContainerLayout.setOrientation(LinearLayout.VERTICAL);
46 | skuContainerLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT));
47 | addView(skuContainerLayout);
48 | }
49 |
50 | /**
51 | * 设置监听接口
52 | *
53 | * @param listener {@link OnSkuListener}
54 | */
55 | public void setOnSkuListener(OnSkuListener listener) {
56 | this.listener = listener;
57 | }
58 |
59 | /**
60 | * 绑定sku数据
61 | *
62 | * @param skuList
63 | */
64 | public void setSkuList(List skuList) {
65 | this.skuList = skuList;
66 | // 清空sku视图
67 | skuContainerLayout.removeAllViews();
68 |
69 | // 获取分组的sku集合
70 | Map> dataMap = getSkuGroupByName(skuList);
71 | selectedAttributeList = new LinkedList<>();
72 | int index = 0;
73 | for (Iterator>> it = dataMap.entrySet().iterator(); it.hasNext(); ) {
74 | Map.Entry> entry = it.next();
75 |
76 | // 构建sku视图
77 | SkuItemLayout itemLayout = new SkuItemLayout(getContext());
78 | itemLayout.setLayoutParams(new LinearLayout.LayoutParams(
79 | LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
80 | ));
81 | itemLayout.buildItemLayout(index++, entry.getKey(), entry.getValue());
82 | itemLayout.setOnSkuItemSelectListener(this);
83 | skuContainerLayout.addView(itemLayout);
84 | // 初始状态下,所有属性信息设置为空
85 | selectedAttributeList.add(new SkuAttribute(entry.getKey(), ""));
86 | }
87 | // 一个sku时,默认选中
88 | if (skuList.size() == 1) {
89 | selectedAttributeList.clear();
90 | for (SkuAttribute attribute : this.skuList.get(0).getAttributes()) {
91 | selectedAttributeList.add(new SkuAttribute(attribute.getKey(), attribute.getValue()));
92 | }
93 | }
94 | // 清除所有选中状态
95 | clearAllLayoutStatus();
96 | // 设置是否可点击
97 | optionLayoutEnableStatus();
98 | // 设置选中状态
99 | optionLayoutSelectStatus();
100 | }
101 |
102 | /**
103 | * 将sku根据属性名进行分组
104 | *
105 | * @param list
106 | * @return 如{ "颜色": {"白色", "红色", "黑色"}, "尺寸": {"XS","S","M", "L", "XL", "XXL"}}
107 | */
108 | private Map> getSkuGroupByName(List list) {
109 | Map> dataMap = new LinkedHashMap<>();
110 | for (Sku sku : list) {
111 | for (SkuAttribute attribute : sku.getAttributes()) {
112 | String attributeName = attribute.getKey();
113 | String attributeValue = attribute.getValue();
114 | if (!dataMap.containsKey(attributeName)) {
115 | dataMap.put(attributeName, new LinkedList());
116 | }
117 | List valueList = dataMap.get(attributeName);
118 | if (!valueList.contains(attributeValue)) {
119 | dataMap.get(attributeName).add(attributeValue);
120 | }
121 | }
122 | }
123 | return dataMap;
124 | }
125 |
126 | /**
127 | * 重置所有属性的选中状态
128 | */
129 | private void clearAllLayoutStatus() {
130 | int skuContainerLayoutChildCount = skuContainerLayout.getChildCount();
131 | for (int i = 0; i < skuContainerLayoutChildCount; i++) {
132 | SkuItemLayout itemLayout = (SkuItemLayout) skuContainerLayout.getChildAt(i);
133 | itemLayout.clearItemViewStatus();
134 | }
135 | }
136 |
137 | /**
138 | * 设置所有属性的Enable状态,即是否可点击
139 | */
140 | private void optionLayoutEnableStatus() {
141 | int childCount = skuContainerLayout.getChildCount();
142 | if (childCount <= 1) {
143 | optionLayoutEnableStatusSingleProperty();
144 | } else {
145 | optionLayoutEnableStatusMultipleProperties();
146 | }
147 | }
148 |
149 | private void optionLayoutEnableStatusSingleProperty() {
150 | SkuItemLayout itemLayout = (SkuItemLayout) skuContainerLayout.getChildAt(0);
151 | // 遍历sku列表
152 | for (Sku sku : skuList) {
153 | // 属性值是否可点击flag
154 | List attributeBeanList = sku.getAttributes();
155 | //库存判断
156 | if (sku.getStockQuantity() > 0) {
157 | String attributeValue = attributeBeanList.get(0).getValue();
158 | itemLayout.optionItemViewEnableStatus(attributeValue);
159 | }
160 | }
161 | }
162 |
163 | private void optionLayoutEnableStatusMultipleProperties() {
164 | int skuContainerLayoutSize = skuContainerLayout.getChildCount();
165 | for (int i = 0; i < skuContainerLayoutSize; i++) {
166 | SkuItemLayout itemLayout = (SkuItemLayout) skuContainerLayout.getChildAt(i);
167 | // 遍历sku列表
168 | int skuListSize = skuList.size();
169 | for (int j = 0; j < skuListSize; j++) {
170 | // 属性值是否可点击flag
171 | boolean flag = false;
172 | Sku sku = skuList.get(j);
173 | List attributeBeanList = sku.getAttributes();
174 | // 遍历选中信息列表
175 | int selectedAttributeListSize = selectedAttributeList.size();
176 | for (int k = 0; k < selectedAttributeListSize; k++) {
177 | // i = k,跳过当前属性,避免多次设置是否可点击
178 | if (i == k) continue;
179 | // 选中信息为空,则说明未选中,无法判断是否有不可点击的情形,跳过
180 | if ("".equals(selectedAttributeList.get(k).getValue())) continue;
181 | // 选中信息列表中不包含当前sku的属性,则sku组合不存在,设置为不可点击
182 | // 库存为0,设置为不可点击
183 | if (!selectedAttributeList.get(k).getValue().equals(attributeBeanList.get(k).getValue())
184 | //库存没有
185 | || sku.getStockQuantity() == 0
186 | ) {
187 | flag = true;
188 | break;
189 | }
190 | }
191 | // flag 为false时,可点击
192 | if (!flag) {
193 | String attributeValue = attributeBeanList.get(i).getValue();
194 | itemLayout.optionItemViewEnableStatus(attributeValue);
195 | }
196 | }
197 | }
198 | }
199 |
200 | /**
201 | * 设置所有属性的选中状态
202 | */
203 | private void optionLayoutSelectStatus() {
204 | int skuContainerLayoutSize = skuContainerLayout.getChildCount();
205 | for (int i = 0; i < skuContainerLayoutSize; i++) {
206 | SkuItemLayout itemLayout = (SkuItemLayout) skuContainerLayout.getChildAt(i);
207 | itemLayout.optionItemViewSelectStatus(selectedAttributeList.get(i));
208 | }
209 | }
210 |
211 | /**
212 | * 是否有sku选中
213 | *
214 | * @return
215 | */
216 | private boolean isSkuSelected() {
217 | for (SkuAttribute attribute : selectedAttributeList) {
218 | if (TextUtils.isEmpty(attribute.getValue())) {
219 | return false;
220 | }
221 | }
222 | return true;
223 | }
224 |
225 | /**
226 | * 获取第一个未选中的属性名
227 | *
228 | * @return
229 | */
230 | public String getFirstUnelectedAttributeName() {
231 | int skuContainerLayoutSize = skuContainerLayout.getChildCount();
232 | for (int i = 0; i < skuContainerLayoutSize; i++) {
233 | SkuItemLayout itemLayout = (SkuItemLayout) skuContainerLayout.getChildAt(i);
234 | if (!itemLayout.isSelected()) {
235 | return itemLayout.getAttributeName();
236 | }
237 | }
238 | return "";
239 | }
240 |
241 | /**
242 | * 获取选中的Sku
243 | *
244 | * @return
245 | */
246 | public Sku getSelectedSku() {
247 | // 判断是否有选中的sku
248 | if (!isSkuSelected()) {
249 | return null;
250 | }
251 | for (Sku sku : skuList) {
252 | List attributeList = sku.getAttributes();
253 | // 将sku的属性列表与selectedAttributeList匹配,完全匹配则为已选中sku
254 | boolean flag = true;
255 | int attributeListSize = attributeList.size();
256 | for (int i = 0; i < attributeListSize; i++) {
257 | if (!isSameSkuAttribute(attributeList.get(i), selectedAttributeList.get(i))) {
258 | flag = false;
259 | }
260 | }
261 | if (flag) {
262 | return sku;
263 | }
264 | }
265 | return null;
266 | }
267 |
268 | /**
269 | * 设置选中的sku
270 | *
271 | * @param sku
272 | */
273 | public void setSelectedSku(Sku sku) {
274 | selectedAttributeList.clear();
275 | for (SkuAttribute attribute : sku.getAttributes()) {
276 | selectedAttributeList.add(new SkuAttribute(attribute.getKey(), attribute.getValue()));
277 | }
278 | // 清除所有选中状态
279 | clearAllLayoutStatus();
280 | // 设置是否可点击
281 | optionLayoutEnableStatus();
282 | // 设置选中状态
283 | optionLayoutSelectStatus();
284 | }
285 |
286 | /**
287 | * 是否为同一个SkuAttribute
288 | *
289 | * @param previousAttribute
290 | * @param nextAttribute
291 | * @return
292 | */
293 | private boolean isSameSkuAttribute(SkuAttribute previousAttribute, SkuAttribute nextAttribute) {
294 | return previousAttribute.getKey().equals(nextAttribute.getKey())
295 | && previousAttribute.getValue().equals(nextAttribute.getValue());
296 | }
297 |
298 | @Override
299 | public void onSelect(int position, boolean selected, SkuAttribute attribute) {
300 | if (selected) {
301 | // 选中,保存选中信息
302 | selectedAttributeList.set(position, attribute);
303 | } else {
304 | // 取消选中,清空保存的选中信息
305 | selectedAttributeList.get(position).setValue("");
306 | }
307 | clearAllLayoutStatus();
308 | // 设置是否可点击
309 | optionLayoutEnableStatus();
310 | // 设置选中状态
311 | optionLayoutSelectStatus();
312 | // 回调接口
313 | if (isSkuSelected()) {
314 | listener.onSkuSelected(getSelectedSku());
315 | } else if (selected) {
316 | listener.onSelect(attribute);
317 | } else {
318 | listener.onUnselected(attribute);
319 | }
320 | }
321 |
322 | /**
323 | * 获取当前选择的属性信息
324 | *
325 | * @return
326 | */
327 | public List getSelectedAttributeList() {
328 | return selectedAttributeList;
329 | }
330 | }
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_in_from_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_out_to_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/color/quantity_operator_text_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/color/sku_item_text_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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/shape_mfe5002.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sku_item_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 | -
15 |
16 |
17 |
18 |
19 |
20 |
21 | -
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sku_quantity_input_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sku_quantity_minus_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
8 |
9 |
10 | -
11 |
12 |
13 |
15 |
16 |
17 | -
18 |
19 |
20 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sku_quantity_plus_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
8 |
9 |
10 | -
11 |
12 |
13 |
15 |
16 |
17 | -
18 |
19 |
20 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_sku.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
17 |
18 |
24 |
25 |
28 |
29 |
30 |
39 |
40 |
41 |
54 |
55 |
56 |
66 |
67 |
68 |
83 |
84 |
85 |
86 |
87 |
91 |
92 |
99 |
100 |
104 |
105 |
116 |
117 |
128 |
129 |
139 |
140 |
151 |
152 |
171 |
172 |
184 |
185 |
186 |
187 |
202 |
203 |
204 |
205 |
206 |
--------------------------------------------------------------------------------
/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/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/comm_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-xhdpi/comm_close.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/comm_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-xxhdpi/comm_close.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 | #2f2f39
7 | #332F2F39
8 | #0A2F2F39
9 | #FE5002
10 | #66FE5002
11 | #14FE5002
12 | #bbbbbb
13 | #666666
14 | #333333
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4dp
4 | 1dp
5 | 152dp
6 | 120dp
7 | 16dp
8 | 24dp
9 | 8dp
10 | 20sp
11 | 14sp
12 | 16sp
13 | 40dp
14 | 6dp
15 | 26dp
16 | 10dp
17 | 12sp
18 | 18sp
19 | 28dp
20 | 2dp
21 | 100dp
22 | 21dp
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SKU
3 | ¥ %s
4 | 确认购买
5 | 购买数量
6 | (库存:%1$s)
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
22 |
23 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/test/java/com/madreain/sku/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.madreain.sku;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.4.1'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 |
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/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 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/sku/sku.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madreain/SKU/f01420e51194f4778cb7b0808b6487b857e9367a/sku/sku.gif
--------------------------------------------------------------------------------