├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── taylor
│ │ └── com
│ │ └── selector
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── taylor
│ │ │ └── com
│ │ │ ├── game
│ │ │ ├── GameBean.kt
│ │ │ └── GameDialogFragment.kt
│ │ │ ├── layout
│ │ │ ├── Layout.kt
│ │ │ └── LineFeedLayout.kt
│ │ │ └── selector
│ │ │ ├── AgeSelector.java
│ │ │ ├── MainActivity.java
│ │ │ ├── OrderSelector.java
│ │ │ └── SelectorKtActivity.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── age_selctor_shape.xml
│ │ ├── age_selector.xml
│ │ ├── bg_game_attr.xml
│ │ ├── bg_game_attr_select.xml
│ │ ├── duck.jpg
│ │ ├── game_selected.xml
│ │ ├── ic_close_black.webp
│ │ ├── ic_launcher_background.xml
│ │ ├── love.png
│ │ ├── man.png
│ │ ├── mushroom.jpeg
│ │ ├── old_man.png
│ │ ├── pasta.jpeg
│ │ ├── pizza.jpeg
│ │ ├── pork.jpg
│ │ ├── scampi.jpeg
│ │ ├── spring_roll.jpeg
│ │ └── teenage.png
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── age_selector.xml
│ │ └── order_selector.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── man.png
│ │ ├── old_man.png
│ │ └── teenage.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── taylor
│ └── com
│ └── selector
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── selector2
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── taylor
│ │ └── com
│ │ └── selector2
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── taylor
│ │ │ └── com
│ │ │ ├── selector2
│ │ │ ├── Selector.java
│ │ │ └── SelectorGroup.java
│ │ │ └── slectorkt
│ │ │ ├── Selector.kt
│ │ │ └── SelectorGroup.kt
│ └── res
│ │ └── values
│ │ ├── attrs.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── taylor
│ └── com
│ └── selector2
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches/build_file_checksums.ser
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/*
9 | .DS_Store
10 | /build
11 | /captures
12 | .externalNativeBuild
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Selector
2 |
3 | it combine different choice mode into one View. There are two default mode, one is single choice mode act like `RadioButton + RadioGroup`, the other is multiple choice mode act like `CheckBox`. The Choice mode could be extended dynamically.
4 |
5 | ### why this?
6 | 1. the RadioButton + RadioGroup has limitation on it's items, they have to be honrizontal or vertical. Use this to get rid of it. You could arrange items whatever you like in layout xml
7 | 2. it combine different choice mode into one View. Single choice, multiple choice and you could design the mode you like. May be it is an "order mode", which is a combination of single and multiple choice.(just like the case when ordering dinner)
8 | 3. you could add Animation for selected and unselected state.
9 | 4. you could design what radio button looks like by a layout xml.
10 |
11 | ### how does it looks it
12 | let me show you the "order mode":
13 | [order-choice.gif](https://user-gold-cdn.xitu.io/2019/5/19/16ad08e61cd8205b?w=960&h=640&f=gif&s=2127202)
14 |
15 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'kotlin-kapt'
5 | android {
6 | compileSdkVersion 28
7 | defaultConfig {
8 | applicationId "taylor.com.selector"
9 | minSdkVersion 15
10 | targetSdkVersion 28
11 | versionCode 1
12 | versionName "1.0"
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | implementation fileTree(dir: 'libs', include: ['*.jar'])
25 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
26 | testImplementation 'junit:junit:4.12'
27 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
28 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
29 | implementation project(":selector2")
30 | implementation 'androidx.appcompat:appcompat:1.1.0'
31 | implementation 'androidx.core:core-ktx:1.2.0'
32 | //kotlin
33 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/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/taylor/com/selector/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package taylor.com.selector;
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("taylor.com.selector", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/game/GameBean.kt:
--------------------------------------------------------------------------------
1 | package cn.neoclub.uki.home.game
2 |
3 | import java.io.Closeable
4 |
5 | data class GameType(
6 | var name: String?,
7 | var img: String?
8 | ) : Closeable {
9 | override fun close() {
10 | name = null
11 | img = null
12 | }
13 | }
14 |
15 | data class Games(
16 | var title: String?,
17 | var gameTypes: List?
18 | )
19 |
20 | data class GameAttrs(
21 | var title: String?,
22 | var attrs: List?
23 | )
24 |
25 | data class GameAttrName(
26 | var name: String?
27 | ) : Closeable {
28 | override fun close() {
29 | name = null
30 | }
31 | }
32 |
33 | data class GameBean(
34 | var games: Games?,
35 | var gameAttrs: List?
36 | )
37 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/game/GameDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package cn.neoclub.uki.home.game
2 |
3 | import android.os.Bundle
4 | import android.support.constraint.ConstraintLayout
5 | import android.util.Log
6 | import android.view.*
7 | import android.widget.LinearLayout
8 | import android.widget.TextView
9 | import androidx.fragment.app.DialogFragment
10 | import taylor.com.layout.*
11 | import taylor.com.selector.R
12 | import taylor.com.slectorkt.Selector
13 | import taylor.com.slectorkt.SelectorGroup
14 |
15 | class GameDialogFragment : DialogFragment() {
16 |
17 | private val gameBeans = GameBean(
18 | Games(
19 | "选择游戏", listOf(
20 | GameType("王者荣耀", "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2284617148,198865717&fm=26&gp=0.jpg"),
21 | GameType("王者荣耀", "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2284617148,198865717&fm=26&gp=0.jpg"),
22 | GameType("王者荣耀", "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2284617148,198865717&fm=26&gp=0.jpg"),
23 | GameType("大神陪你玩", "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2284617148,198865717&fm=26&gp=0.jpg")
24 | )
25 | ),
26 | listOf(
27 | GameAttrs(
28 | "大区", listOf(
29 | // GameAttrName("不限"),
30 | GameAttrName("微信"),
31 | GameAttrName("QQ")
32 | )
33 | ),
34 | GameAttrs(
35 | "模式", listOf(
36 | // GameAttrName("不限"),
37 | GameAttrName("排位赛"),
38 | GameAttrName("普通模式"),
39 | GameAttrName("娱乐模式"),
40 | GameAttrName("游戏交流")
41 | )
42 | ),
43 | GameAttrs(
44 | "匹配段位", listOf(
45 | // GameAttrName("不限"),
46 | GameAttrName("青铜白银"),
47 | GameAttrName("黄金"),
48 | GameAttrName("铂金"),
49 | GameAttrName("钻石"),
50 | GameAttrName("星耀"),
51 | GameAttrName("王者")
52 | )
53 | ),
54 | GameAttrs(
55 | "组队人数", listOf(
56 | // GameAttrName("不限"),
57 | GameAttrName("三排"),
58 | GameAttrName("五排")
59 | )
60 | )
61 | )
62 | )
63 |
64 | private val DEFAULT_SELECTS = setOf(
65 | "微信",
66 | "排位赛"
67 | )
68 | private var matchContent: String = ""
69 |
70 | private var selectedTags = listOf()
71 |
72 | //
73 | private lateinit var gameContainer: LinearLayout
74 |
75 | private val rootView: ConstraintLayout? by lazy {
76 | ConstraintLayout {
77 | layout_width = match_parent
78 | layout_height = 650
79 | background_color = "#FFFFFF"
80 |
81 | gameContainer = LinearLayout {
82 | layout_id = "gameContainer"
83 | layout_width = match_parent
84 | layout_height = 573
85 | padding_start = 20
86 | padding_end = 5
87 | orientation = vertical
88 | top_toTopOf = parent_id
89 | }
90 |
91 | ImageView {
92 | layout_width = 22
93 | layout_height = 22
94 | top_toTopOf = parent_id
95 | end_toEndOf = parent_id
96 | margin_top = 9
97 | margin_end = 5
98 | padding = 5
99 | scaleType = scale_fix_xy
100 | src = R.drawable.ic_close_black
101 | onClick = onCloseClick
102 | }
103 |
104 | when (arguments?.getString("type")) {
105 | "match" -> {
106 | TextView {
107 | layout_id = "tvCreateParty"
108 | layout_width = 150
109 | layout_height = wrap_content
110 | textSize = 14f
111 | textColor = "#ff3f4658"
112 | background_res = R.drawable.bg_game_attr
113 | padding_top = 15
114 | padding_bottom = 15
115 | text = "关闭"
116 | horizontal_chain_style = spread
117 | gravity = gravity_center
118 | start_toStartOf = parent_id
119 | end_toStartOf = "tvStartMatch"
120 | top_toBottomOf = "gameContainer"
121 | bottom_toBottomOf = parent_id
122 | onClick = { _ -> }
123 | }
124 |
125 | TextView {
126 | layout_id = "tvStartMatch"
127 | layout_width = 150
128 | layout_height = wrap_content
129 | textSize = 14f
130 | textColor = "#FFFFFF"
131 | background_res = R.drawable.bg_game_attr_select
132 | padding_top = 15
133 | padding_bottom = 15
134 | text = "匹配"
135 | gravity = gravity_center
136 | horizontal_chain_style = spread
137 | start_toEndOf = "tvCreateParty"
138 | end_toEndOf = parent_id
139 | top_toBottomOf = "gameContainer"
140 | bottom_toBottomOf = parent_id
141 | onClick = { _ -> }
142 | }
143 | }
144 | "party" -> {
145 | TextView {
146 | layout_width = 335
147 | layout_height = 60
148 | textSize = 14f
149 | textColor = "#FFFFFF"
150 | gravity = gravity_center
151 | text = "创建组队"
152 | background_res = R.drawable.bg_game_attr_select
153 | center_horizontal = true
154 | top_toBottomOf = "gameContainer"
155 | bottom_toBottomOf = parent_id
156 | onClick = { _ -> }
157 | }
158 | }
159 | "info" -> {
160 | TextView {
161 | layout_id = "tvDelete"
162 | layout_width = 150
163 | layout_height = wrap_content
164 | textSize = 14f
165 | textColor = "#3F4658"
166 | background_res = R.drawable.bg_game_attr
167 | padding_top = 15
168 | padding_bottom = 15
169 | horizontal_chain_style = spread
170 | text = "关闭"
171 | gravity = gravity_center
172 | start_toStartOf = parent_id
173 | end_toStartOf = "tvSave"
174 | top_toBottomOf = "gameContainer"
175 | bottom_toBottomOf = parent_id
176 | onClick = { _ -> }
177 | }
178 |
179 | TextView {
180 | layout_id = "tvSave"
181 | layout_width = 150
182 | layout_height = wrap_content
183 | textSize = 14f
184 | textColor = "#FFFFFF"
185 | background_res = R.drawable.bg_game_attr_select
186 | padding_top = 15
187 | padding_bottom = 15
188 | gravity = gravity_center
189 | horizontal_chain_style = spread
190 | text = "保存"
191 | start_toEndOf = "tvDelete"
192 | end_toEndOf = parent_id
193 | top_toBottomOf = "gameContainer"
194 | bottom_toBottomOf = parent_id
195 | onClick = { _ -> }
196 | }
197 | }
198 | else -> {
199 | TextView {
200 | layout_width = 320
201 | layout_height = 60
202 | textSize = 14f
203 | textColor = "#FFFFFF"
204 | gravity = gravity_center
205 | text = "创建组队"
206 | background_res = R.drawable.bg_game_attr_select
207 | center_horizontal = true
208 | top_toBottomOf = "gameContainer"
209 | bottom_toBottomOf = parent_id
210 | onClick = { _ -> }
211 | }
212 | }
213 | }
214 | }
215 | }
216 |
217 |
218 | private val titleView: TextView?
219 | get() = TextView {
220 | layout_width = wrap_content
221 | layout_height = wrap_content
222 | textSize = 14f
223 | textColor = "#ff3f4658"
224 | textStyle = bold
225 | }
226 |
227 | private val gameView: ConstraintLayout?
228 | get() = ConstraintLayout {
229 | layout_width = 60
230 | layout_height = 81
231 |
232 | View {
233 | layout_id = "vGameSelected"
234 | layout_width = 60
235 | layout_height = 60
236 | top_toTopOf = parent_id
237 | center_horizontal = true
238 | visibility = invisible
239 | background_res = R.drawable.game_selected
240 | }
241 |
242 | ImageView {
243 | layout_id = "ivGame"
244 | layout_width = 50
245 | layout_height = 50
246 | align_horizontal_to = "vGameSelected"
247 | align_vertical_to = "vGameSelected"
248 | scaleType = scale_fix_xy
249 | }
250 |
251 | TextView {
252 | layout_id = "tvGame"
253 | layout_width = wrap_content
254 | layout_height = wrap_content
255 | bottom_toBottomOf = parent_id
256 | textColor = "#747E8B"
257 | textSize = 12f
258 | center_horizontal = true
259 | }
260 | }
261 |
262 | private val gameAttrView: TextView?
263 | get() = TextView {
264 | layout_id = "tvGameAttrName"
265 | layout_width = 70
266 | layout_height = 32
267 | textSize = 12f
268 | textColor = "#ff3f4658"
269 | background_res = R.drawable.bg_game_attr
270 | gravity = gravity_center
271 | padding_top = 7
272 | padding_bottom = 7
273 | }
274 |
275 | //
276 |
277 | //
278 | private val onCloseClick = { _: View ->
279 | dismiss()
280 | }
281 |
282 | private val onGameTypeChange = { selector: Selector, select: Boolean ->
283 | selector.find("vGameSelected")?.visibility = if (select) visible else invisible
284 | selector.find("tvGame")?.textColor = if (select) "#FF5183" else "#747E8B"
285 | }
286 |
287 | private val onGameAttrChange = { selector: Selector, select: Boolean ->
288 | selector.find("tvGameAttrName")?.apply {
289 | background_res = if (select) R.drawable.bg_game_attr_select else R.drawable.bg_game_attr
290 | textColor = if (select) "#FFFFFF" else "#3F4658"
291 | }
292 | Unit
293 | }
294 |
295 | private val gameSelectorGroup by lazy {
296 | SelectorGroup().apply {
297 | choiceMode = { selectorGroup, selector,map ->
298 | if (selector.groupTag != "匹配段位") {
299 | selectorGroup.apply {
300 | findLast(selector.groupTag)?.let { setSelected(it, false) }
301 | }
302 | selectorGroup.setSelected(selector, true)
303 | } else {
304 | selectorGroup.setSelected(selector, !selector.isSelecting)
305 | }
306 | }
307 | selectChangeListener = { selecteds ->
308 | selectedTags = selecteds.map { it.tag }
309 | Log.v("ttaylor", "tag=asdf, GameDialogFragment.() selectors = ${selecteds.print { it.tag }}")
310 | Log.d("ttaylor", "tag=asdf, GameDialogFragment.() selectors = ${selectedTags.print { it }}")
311 | }
312 | }
313 | }
314 | //
315 |
316 | override fun onCreate(savedInstanceState: Bundle?) {
317 | super.onCreate(savedInstanceState)
318 | dialog?.window?.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
319 | }
320 |
321 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
322 | return rootView
323 | }
324 |
325 | override fun onActivityCreated(savedInstanceState: Bundle?) {
326 | super.onActivityCreated(savedInstanceState)
327 | dialog?.window?.attributes?.apply {
328 | width = WindowManager.LayoutParams.MATCH_PARENT
329 | height = WindowManager.LayoutParams.WRAP_CONTENT
330 | gravity = Gravity.BOTTOM
331 | }
332 | }
333 |
334 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
335 | super.onViewCreated(view, savedInstanceState)
336 | rootView?.post {
337 | buildGameLayout(gameBeans, DEFAULT_SELECTS)
338 | }
339 | }
340 |
341 | private fun buildGameLayout(gameBeans: GameBean, defautSelect: Set) {
342 | val gameTypes = gameBeans.games?.gameTypes?.filter { !it.name.isNullOrEmpty() }
343 | val gameAttrs = gameBeans.gameAttrs?.filter { !it.title.isNullOrEmpty() }
344 |
345 | gameContainer.apply {
346 | // build game attribute
347 | gameAttrs?.forEach { gameAttr ->
348 | titleView?.apply {
349 | text = gameAttr.title
350 | margin_top = 10
351 | margin_bottom = 10
352 | }.also { addView(it) }
353 |
354 | LineFeedLayout {
355 | layout_width = match_parent
356 | layout_height = wrap_content
357 | horizontal_gap = 8
358 | vertical_gap = 8
359 |
360 | gameAttr.attrs?.forEachIndexed { index, attr ->
361 | Selector {
362 | layout_id = attr.name!!
363 | tag = attr.name!!
364 | groupTag = gameAttr.title!!
365 | group = gameSelectorGroup
366 | contentView = gameAttrView
367 | onSelectChange = onGameAttrChange
368 | layout_width = 70
369 | layout_height = 32
370 | bind = Binder(attr) { _, _ ->
371 | this[gameAttrKey] = attr
372 | find("tvGameAttrName")?.text = attr.name
373 | }
374 | }.takeIf { attr.name in defautSelect }?.also { gameSelectorGroup.setSelected(it,true) }
375 | }
376 | }
377 | }
378 | }
379 | }
380 |
381 | private val gameTypeKey = object : Selector.Key {}
382 | private val gameAttrKey = object : Selector.Key {}
383 | }
384 |
385 | /**
386 | * print collection bean in which you interested defined by [map]
387 | */
388 | fun Collection.print(map: (T) -> String) =
389 | StringBuilder("\n[").also { sb ->
390 | this.forEach { e -> sb.append("\n\t${map(e)},") }
391 | sb.append("\n]")
392 | }.toString()
393 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/layout/Layout.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.layout
2 |
3 |
4 | import android.content.Context
5 | import android.content.res.Resources
6 | import android.graphics.Color
7 | import android.graphics.Typeface
8 | import android.support.constraint.ConstraintHelper
9 | import android.support.constraint.ConstraintLayout
10 | import android.support.constraint.Guideline
11 |
12 | import android.text.Editable
13 | import android.util.TypedValue
14 | import android.view.*
15 | import android.widget.*
16 | import androidx.core.content.res.ResourcesCompat
17 | import androidx.core.view.MarginLayoutParamsCompat
18 | import androidx.fragment.app.Fragment
19 | import androidx.lifecycle.LifecycleOwner
20 | import androidx.lifecycle.LiveData
21 | import androidx.lifecycle.Observer
22 | import taylor.com.slectorkt.Selector
23 |
24 |
25 | //
26 | inline fun ViewGroup.TextView(init: TextView.() -> Unit) =
27 | TextView(context).apply(init).also { addView(it) }
28 |
29 | inline fun ViewGroup.ImageView(init: ImageView.() -> Unit) =
30 | ImageView(context).apply(init).also { addView(it) }
31 |
32 | inline fun ViewGroup.Button(init: Button.() -> Unit) =
33 | Button(context).apply(init).also { addView(it) }
34 |
35 | inline fun ViewGroup.View(init: View.() -> Unit): View =
36 | View(context).apply(init).also { addView(it) }
37 |
38 | inline fun ViewGroup.RelativeLayout(init: RelativeLayout.() -> Unit) =
39 | RelativeLayout(context).apply(init).also { addView(it) }
40 |
41 | inline fun ViewGroup.LinearLayout(init: LinearLayout.() -> Unit) =
42 | LinearLayout(context).apply(init).also { addView(it) }
43 |
44 | inline fun ViewGroup.ConstraintLayout(init: ConstraintLayout.() -> Unit) =
45 | ConstraintLayout(context).apply(init).also { addView(it) }
46 |
47 | inline fun ViewGroup.FrameLayout(init: FrameLayout.() -> Unit) =
48 | FrameLayout(context).apply(init).also { addView(it) }
49 |
50 | inline fun ViewGroup.ViewFlipper(init: ViewFlipper.() -> Unit) =
51 | ViewFlipper(context).apply(init).also { addView(it) }
52 |
53 | inline fun ViewGroup.EditText(init: EditText.() -> Unit) =
54 | EditText(context).apply(init).also { addView(it) }
55 |
56 | inline fun ViewGroup.Selector(init: Selector.() -> Unit) =
57 | Selector(context).apply(init).also { addView(it) }
58 |
59 | inline fun ConstraintLayout.Guideline(init: Guideline.() -> Unit) =
60 | Guideline(context).apply(init).also { addView(it) }
61 |
62 | inline fun Context.ConstraintLayout(init: ConstraintLayout.() -> Unit): ConstraintLayout =
63 | ConstraintLayout(this).apply(init)
64 |
65 | inline fun Context.LinearLayout(init: LinearLayout.() -> Unit): LinearLayout =
66 | LinearLayout(this).apply(init)
67 |
68 | inline fun Context.FrameLayout(init: FrameLayout.() -> Unit) =
69 | FrameLayout(this).apply(init)
70 |
71 |
72 | inline fun Context.TextView(init: TextView.() -> Unit) =
73 | TextView(this).apply(init)
74 |
75 | inline fun Context.Button(init: Button.() -> Unit) =
76 | Button(this).apply(init)
77 |
78 | inline fun Context.ImageView(init: ImageView.() -> Unit) =
79 | ImageView(this).apply(init)
80 |
81 | inline fun Context.View(init: View.() -> Unit) =
82 | View(this).apply(init)
83 |
84 | inline fun Context.EditText(init: EditText.() -> Unit) =
85 | EditText(this).apply(init)
86 |
87 | inline fun Context.ViewFlipper(init: ViewFlipper.() -> Unit) =
88 | ViewFlipper(this).apply(init)
89 |
90 | inline fun Context.Selector(init: Selector.() -> Unit) =
91 | Selector(this).apply(init)
92 |
93 | inline fun Fragment.ConstraintLayout(init: ConstraintLayout.() -> Unit) =
94 | context?.let { ConstraintLayout(it).apply(init) }
95 |
96 | inline fun Fragment.LinearLayout(init: LinearLayout.() -> Unit) =
97 | context?.let { LinearLayout(it).apply(init) }
98 |
99 | inline fun Fragment.FrameLayout(init: FrameLayout.() -> Unit) =
100 | context?.let { FrameLayout(it).apply(init) }
101 |
102 |
103 | inline fun Fragment.TextView(init: TextView.() -> Unit) =
104 | context?.let { TextView(it).apply(init) }
105 |
106 | inline fun Fragment.Button(init: Button.() -> Unit) =
107 | context?.let { Button(it).apply(init) }
108 |
109 | inline fun Fragment.ImageView(init: ImageView.() -> Unit) =
110 | context?.let { ImageView(it).apply(init) }
111 |
112 | inline fun Fragment.View(init: View.() -> Unit) =
113 | context?.let { View(it).apply(init) }
114 |
115 | inline fun Fragment.ViewFlipper(init: ViewFlipper.() -> Unit) =
116 | context?.let { ViewFlipper(it).apply(init) }
117 |
118 | inline fun Fragment.EditText(init: EditText.() -> Unit) =
119 | context?.let { EditText(it).apply(init) }
120 |
121 | inline fun ViewGroup.LineFeedLayout(init: LineFeedLayout.() -> Unit) =
122 | LineFeedLayout(context).apply(init).also { addView(it) }
123 |
124 | inline fun Context.LineFeedLayout(init: LineFeedLayout.() -> Unit): LineFeedLayout =
125 | LineFeedLayout(this).apply(init)
126 |
127 | inline fun Fragment.LineFeedLayout(init: LineFeedLayout.() -> Unit) =
128 | context?.let { LineFeedLayout(it).apply(init) }
129 |
130 | //
131 |
132 | //
133 | inline var View.layout_id: String
134 | get() {
135 | return ""
136 | }
137 | set(value) {
138 | id = value.toLayoutId()
139 | }
140 | inline var View.padding_top: Int
141 | get() {
142 | return 0
143 | }
144 | set(value) {
145 | setPadding(paddingLeft, value.dp, paddingRight, paddingBottom)
146 | }
147 |
148 | inline var View.padding_bottom: Int
149 | get() {
150 | return 0
151 | }
152 | set(value) {
153 | setPadding(paddingLeft, paddingTop, paddingRight, value.dp)
154 | }
155 |
156 | inline var View.padding_start: Int
157 | get() {
158 | return 0
159 | }
160 | set(value) {
161 | setPadding(value.dp, paddingTop, paddingRight, paddingBottom)
162 | }
163 |
164 | inline var View.padding_end: Int
165 | get() {
166 | return 0
167 | }
168 | set(value) {
169 | setPadding(paddingLeft, paddingTop, value.dp, paddingBottom)
170 | }
171 | inline var View.padding: Int
172 | get() {
173 | return 0
174 | }
175 | set(value) {
176 | setPadding(value.dp, value.dp, value.dp, value.dp)
177 | }
178 | inline var View.layout_width: Int
179 | get() {
180 | return 0
181 | }
182 | set(value) {
183 | val w = if (value > 0) value.dp else value
184 | val h = layoutParams?.height ?: 0
185 | layoutParams = ViewGroup.MarginLayoutParams(w, h)
186 | }
187 |
188 | inline var View.layout_height: Int
189 | get() {
190 | return 0
191 | }
192 | set(value) {
193 |
194 | val w = layoutParams?.width ?: 0
195 | val h = if (value > 0) value.dp else value
196 | layoutParams = ViewGroup.MarginLayoutParams(w, h)
197 | }
198 |
199 | inline var View.alignParentStart: Boolean
200 | get() {
201 | return false
202 | }
203 | set(value) {
204 | if (!value) return
205 | layoutParams = RelativeLayout.LayoutParams(layoutParams.width, layoutParams.height).apply {
206 | (layoutParams as? RelativeLayout.LayoutParams)?.rules?.forEachIndexed { index, i ->
207 | addRule(index, i)
208 | }
209 | addRule(RelativeLayout.ALIGN_PARENT_START, RelativeLayout.TRUE)
210 | }
211 | }
212 |
213 | inline var View.alignParentEnd: Boolean
214 | get() {
215 | return false
216 | }
217 | set(value) {
218 | if (!value) return
219 | layoutParams = RelativeLayout.LayoutParams(layoutParams.width, layoutParams.height).apply {
220 | (layoutParams as? RelativeLayout.LayoutParams)?.rules?.forEachIndexed { index, i ->
221 | addRule(index, i)
222 | }
223 | addRule(RelativeLayout.ALIGN_PARENT_END, RelativeLayout.TRUE)
224 | }
225 | }
226 |
227 | inline var View.centerVertical: Boolean
228 | get() {
229 | return false
230 | }
231 | set(value) {
232 | if (!value) return
233 | layoutParams = RelativeLayout.LayoutParams(layoutParams.width, layoutParams.height).apply {
234 | (layoutParams as? RelativeLayout.LayoutParams)?.rules?.forEachIndexed { index, i ->
235 | addRule(index, i)
236 | }
237 | addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE)
238 | }
239 | }
240 |
241 | inline var View.centerInParent: Boolean
242 | get() {
243 | return false
244 | }
245 | set(value) {
246 | if (!value) return
247 | layoutParams = RelativeLayout.LayoutParams(layoutParams.width, layoutParams.height).apply {
248 | (layoutParams as? RelativeLayout.LayoutParams)?.rules?.forEachIndexed { index, i ->
249 | addRule(index, i)
250 | }
251 | addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE)
252 | }
253 | }
254 |
255 | inline var View.weight: Float
256 | get() {
257 | return 0f
258 | }
259 | set(value) {
260 | layoutParams =
261 | LinearLayout.LayoutParams(layoutParams.width, layoutParams.height).also { it ->
262 | it.gravity = (layoutParams as? LinearLayout.LayoutParams)?.gravity ?: -1
263 | it.weight = value
264 | }
265 | }
266 | inline var View.layout_gravity: Int
267 | get() {
268 | return -1
269 | }
270 | set(value) {
271 | layoutParams =
272 | LinearLayout.LayoutParams(layoutParams.width, layoutParams.height).also { it ->
273 | it.weight = (layoutParams as? LinearLayout.LayoutParams)?.weight ?: 0f
274 | it.gravity = value
275 | }
276 | }
277 |
278 | inline var View.start_toStartOf: String
279 | get() {
280 | return ""
281 | }
282 | set(value) {
283 | layoutParams = layoutParams.append {
284 | startToStart = value.toLayoutId()
285 | startToEnd = -1
286 | }
287 | }
288 |
289 | inline var View.start_toEndOf: String
290 | get() {
291 | return ""
292 | }
293 | set(value) {
294 | layoutParams = layoutParams.append {
295 | startToEnd = value.toLayoutId()
296 | startToStart = -1
297 | }
298 | }
299 |
300 | inline var View.top_toBottomOf: String
301 | get() {
302 | return ""
303 | }
304 | set(value) {
305 | layoutParams = layoutParams.append {
306 | topToBottom = value.toLayoutId()
307 | topToTop = -1
308 | }
309 | }
310 |
311 | inline var View.top_toTopOf: String
312 | get() {
313 | return ""
314 | }
315 | set(value) {
316 | layoutParams = layoutParams.append {
317 | topToTop = value.toLayoutId()
318 | topToBottom = -1
319 | }
320 | }
321 |
322 | inline var View.end_toEndOf: String
323 | get() {
324 | return ""
325 | }
326 | set(value) {
327 | layoutParams = layoutParams.append {
328 | endToEnd = value.toLayoutId()
329 | endToStart = -1
330 | }
331 | }
332 |
333 | inline var View.end_toStartOf: String
334 | get() {
335 | return ""
336 | }
337 | set(value) {
338 | layoutParams = layoutParams.append {
339 | endToStart = value.toLayoutId()
340 | endToEnd = -1
341 | }
342 | }
343 |
344 | inline var View.bottom_toBottomOf: String
345 | get() {
346 | return ""
347 | }
348 | set(value) {
349 | layoutParams = layoutParams.append {
350 | bottomToBottom = value.toLayoutId()
351 | bottomToTop = -1
352 | }
353 | }
354 |
355 | inline var View.bottom_toTopOf: String
356 | get() {
357 | return ""
358 | }
359 | set(value) {
360 | layoutParams = layoutParams.append {
361 | bottomToTop = value.toLayoutId()
362 | bottomToBottom = -1
363 | }
364 | }
365 |
366 | inline var View.horizontal_chain_style: Int
367 | get() {
368 | return -1
369 | }
370 | set(value) {
371 | layoutParams = layoutParams.append {
372 | horizontalChainStyle = value
373 | }
374 | }
375 |
376 | inline var View.vertical_chain_style: Int
377 | get() {
378 | return -1
379 | }
380 | set(value) {
381 | layoutParams = layoutParams.append {
382 | verticalChainStyle = value
383 | }
384 | }
385 |
386 | inline var View.horizontal_bias: Float
387 | get() {
388 | return -1f
389 | }
390 | set(value) {
391 | layoutParams = layoutParams.append {
392 | horizontalBias = value
393 | }
394 | }
395 | inline var View.dimension_radio: String
396 | get() {
397 | return ""
398 | }
399 | set(value) {
400 | layoutParams = layoutParams.append {
401 | dimensionRatio = value
402 | }
403 | }
404 |
405 | inline var View.vertical_bias: Float
406 | get() {
407 | return -1f
408 | }
409 | set(value) {
410 | layoutParams = layoutParams.append {
411 | verticalBias = value
412 | }
413 | }
414 |
415 | inline var View.center_horizontal: Boolean
416 | get() {
417 | return false
418 | }
419 | set(value) {
420 | if (!value) return
421 | start_toStartOf = parent_id
422 | end_toEndOf = parent_id
423 | }
424 |
425 | inline var View.center_vertical: Boolean
426 | get() {
427 | return false
428 | }
429 | set(value) {
430 | if (!value) return
431 | top_toTopOf = parent_id
432 | bottom_toBottomOf = parent_id
433 | }
434 |
435 | inline var View.align_vertical_to: String
436 | get() {
437 | return ""
438 | }
439 | set(value) {
440 | top_toTopOf = value
441 | bottom_toBottomOf = value
442 | }
443 |
444 | inline var View.align_horizontal_to: String
445 | get() {
446 | return ""
447 | }
448 | set(value) {
449 | start_toStartOf = value
450 | end_toEndOf = value
451 | }
452 |
453 | inline var View.background_color: String
454 | get() {
455 | return ""
456 | }
457 | set(value) {
458 | setBackgroundColor(Color.parseColor(value))
459 | }
460 |
461 | inline var View.background_res: Int
462 | get() {
463 | return -1
464 | }
465 | set(value) {
466 | setBackgroundResource(value)
467 | }
468 |
469 | inline var View.margin_top: Int
470 | get() {
471 | return -1
472 | }
473 | set(value) {
474 | (layoutParams as? ViewGroup.MarginLayoutParams)?.apply {
475 | topMargin = value.dp
476 | }
477 | }
478 |
479 | inline var View.margin_bottom: Int
480 | get() {
481 | return -1
482 | }
483 | set(value) {
484 | (layoutParams as? ViewGroup.MarginLayoutParams)?.apply {
485 | bottomMargin = value.dp
486 | }
487 | }
488 |
489 | inline var View.margin_start: Int
490 | get() {
491 | return -1
492 | }
493 | set(value) {
494 | (layoutParams as? ViewGroup.MarginLayoutParams)?.apply {
495 | MarginLayoutParamsCompat.setMarginStart(this, value.dp)
496 | }
497 | }
498 |
499 | inline var View.margin_end: Int
500 | get() {
501 | return -1
502 | }
503 | set(value) {
504 | (layoutParams as? ViewGroup.MarginLayoutParams)?.apply {
505 | MarginLayoutParamsCompat.setMarginEnd(this, value.dp)
506 | }
507 | }
508 |
509 | inline var View.layout_visibility: Int
510 | get() {
511 | return -1
512 | }
513 | set(value) {
514 | visibility = value
515 | }
516 |
517 | inline var View.bindLiveData: LiveDataBinder?
518 | get() {
519 | return null
520 | }
521 | set(value) {
522 | observe(value?.liveData) {
523 | value?.action?.invoke(it)
524 | }
525 | }
526 |
527 | inline var View.bind: Binder?
528 | get() {
529 | return null
530 | }
531 | set(value) {
532 | value?.action?.invoke(this, value.data )
533 | }
534 |
535 | inline var ImageView.src: Int
536 | get() {
537 | return -1
538 | }
539 | set(value) {
540 | setImageResource(value)
541 | }
542 |
543 | inline var TextView.textRes: Int
544 | get() {
545 | return -1
546 | }
547 | set(value) {
548 | setText(value)
549 | }
550 |
551 | inline var TextView.textStyle: Int
552 | get() {
553 | return -1
554 | }
555 | set(value) = setTypeface(typeface, value)
556 |
557 | inline var TextView.textColor: String
558 | get() {
559 | return ""
560 | }
561 | set(value) {
562 | setTextColor(Color.parseColor(value))
563 | }
564 |
565 | inline var TextView.fontFamily: Int
566 | get() {
567 | return 1
568 | }
569 | set(value) {
570 | typeface = ResourcesCompat.getFont(context, value)
571 | }
572 |
573 | inline var LineFeedLayout.horizontal_gap: Int
574 | get() {
575 | return -1
576 | }
577 | set(value) {
578 | horizontalGap = value.dp
579 | }
580 |
581 | inline var LineFeedLayout.vertical_gap: Int
582 | get() {
583 | return -1
584 | }
585 | set(value) {
586 | verticalGap = value.dp
587 | }
588 |
589 | inline var TextView.onTextChange: TextWatcher
590 | get() {
591 | return TextWatcher()
592 | }
593 | set(value) {
594 | val textWatcher = object : android.text.TextWatcher {
595 | override fun afterTextChanged(s: Editable?) {
596 | value.afterTextChanged.invoke(s)
597 | }
598 |
599 | override fun beforeTextChanged(
600 | text: CharSequence?,
601 | start: Int,
602 | count: Int,
603 | after: Int
604 | ) {
605 | value.beforeTextChanged.invoke(text, start, count, after)
606 | }
607 |
608 | override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) {
609 | value.onTextChanged.invoke(text, start, before, count)
610 | }
611 | }
612 | addTextChangedListener(textWatcher)
613 | }
614 |
615 | inline var Button.textAllCaps: Boolean
616 | get() {
617 | return false
618 | }
619 | set(value) {
620 | isAllCaps = value
621 | }
622 |
623 | inline var ConstraintHelper.referenceIds: String
624 | get() {
625 | return ""
626 | }
627 | set(value) {
628 | referencedIds = value.split(",").map { it.toLayoutId() }.toIntArray()
629 | }
630 |
631 |
632 | var View.onClick: (View) -> Unit
633 | get() {
634 | return {}
635 | }
636 | set(value) {
637 | setOnClickListener { v -> value(v) }
638 | }
639 |
640 | //
641 |
642 |
643 | //
644 | val match_parent = ViewGroup.LayoutParams.MATCH_PARENT
645 | val wrap_content = ViewGroup.LayoutParams.WRAP_CONTENT
646 |
647 | val visible = View.VISIBLE
648 | val gone = View.GONE
649 | val invisible = View.INVISIBLE
650 |
651 | val horizontal = LinearLayout.HORIZONTAL
652 | val vertical = LinearLayout.VERTICAL
653 |
654 | val bold = Typeface.BOLD
655 | val normal = Typeface.NORMAL
656 | val italic = Typeface.ITALIC
657 | val bold_italic = Typeface.BOLD_ITALIC
658 |
659 | val gravity_center = Gravity.CENTER
660 | val gravity_left = Gravity.LEFT
661 | val gravity_right = Gravity.RIGHT
662 | val gravity_bottom = Gravity.BOTTOM
663 | val gravity_top = Gravity.TOP
664 | val gravity_center_horizontal = Gravity.CENTER_HORIZONTAL
665 | val gravity_center_vertical = Gravity.CENTER_VERTICAL
666 |
667 | val scale_fix_xy = ImageView.ScaleType.FIT_XY
668 | val scale_center_crop = ImageView.ScaleType.CENTER_CROP
669 | val scale_center = ImageView.ScaleType.CENTER
670 | val scale_center_inside = ImageView.ScaleType.CENTER_INSIDE
671 | val scale_fit_center = ImageView.ScaleType.FIT_CENTER
672 | val scale_fit_end = ImageView.ScaleType.FIT_END
673 | val scale_matrix = ImageView.ScaleType.MATRIX
674 | val scale_fit_start = ImageView.ScaleType.FIT_START
675 |
676 |
677 | val spread = ConstraintLayout.LayoutParams.CHAIN_SPREAD
678 | val packed = ConstraintLayout.LayoutParams.CHAIN_PACKED
679 | val spread_inside = ConstraintLayout.LayoutParams.CHAIN_SPREAD_INSIDE
680 |
681 | val parent_id = "0"
682 | //
683 |
684 | //
685 | val Int.dp: Int
686 | get() {
687 | return TypedValue.applyDimension(
688 | TypedValue.COMPLEX_UNIT_DIP,
689 | this.toFloat(),
690 | Resources.getSystem().displayMetrics
691 | ).toInt()
692 | }
693 |
694 | fun ViewGroup.MarginLayoutParams.toConstraintLayoutParam() =
695 | ConstraintLayout.LayoutParams(width, height).also { it ->
696 | it.topMargin = this.topMargin
697 | it.bottomMargin = this.bottomMargin
698 | it.marginStart = this.marginStart
699 | it.marginEnd = this.marginEnd
700 | }
701 |
702 | fun ViewGroup.LayoutParams.append(set: ConstraintLayout.LayoutParams.() -> Unit) =
703 | (this as? ConstraintLayout.LayoutParams)?.apply(set)
704 | ?: (this as? ViewGroup.MarginLayoutParams)?.toConstraintLayoutParam()?.apply(set)
705 |
706 |
707 | fun String.toLayoutId(): Int {
708 | var id = java.lang.String(this).bytes.sum()
709 | if (id == 48) id = 0
710 | return id
711 | }
712 |
713 | fun View.find(id: String): T? = findViewById(id.toLayoutId())
714 |
715 | fun View.observe(liveData: LiveData?, action: (T) -> Unit) {
716 | (context as? LifecycleOwner)?.let { owner ->
717 | liveData?.observe(owner, Observer { action(it) })
718 | }
719 | }
720 |
721 | //
722 |
723 |
724 | //
725 | class TextWatcher(
726 | var beforeTextChanged: (
727 | text: CharSequence?,
728 | start: Int,
729 | count: Int,
730 | after: Int
731 | ) -> Unit = { _, _, _, _ -> },
732 | var onTextChanged: (
733 | text: CharSequence?,
734 | start: Int,
735 | count: Int,
736 | after: Int
737 | ) -> Unit = { _, _, _, _ -> },
738 | var afterTextChanged: (text: Editable?) -> Unit = {}
739 | )
740 |
741 | fun textWatcher(init: TextWatcher.() -> Unit): TextWatcher = TextWatcher().apply(init)
742 |
743 | /**
744 | * helper class for data binding
745 | */
746 | class LiveDataBinder(var liveData: LiveData<*>? = null, var action: ((Any?) -> Unit)? = null)
747 |
748 | fun liveDataBinder(liveData: LiveData<*>?, init: LiveDataBinder.() -> Unit): LiveDataBinder =
749 | LiveDataBinder(liveData).apply(init)
750 |
751 | class Binder(var data: Any?, var action: ((View, Any?) -> Unit)? = null)
752 |
753 |
754 | //
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/layout/LineFeedLayout.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.layout
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.LinearLayout
8 |
9 | /**
10 | * a special [ViewGroup] acts like [LinearLayout],
11 | * it spreads the children from left to right until there is not enough horizontal space for them,
12 | * then the next child will be placed at a new line
13 | */
14 | class LineFeedLayout @JvmOverloads constructor(
15 | context: Context,
16 | attrs: AttributeSet? = null,
17 | defStyleAttr: Int = 0
18 | ) : ViewGroup(context, attrs, defStyleAttr) {
19 |
20 | var horizontalGap: Int = 0
21 | var verticalGap: Int = 0
22 |
23 | /**
24 | * the height of [LineFeedLayout] depends on how much lines it has
25 | */
26 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
27 | measureChildren(widthMeasureSpec, heightMeasureSpec)
28 | val heightMode = MeasureSpec.getMode(heightMeasureSpec)
29 | val width = MeasureSpec.getSize(widthMeasureSpec)
30 | var height = 0
31 | if (heightMode == MeasureSpec.EXACTLY) {
32 | height = MeasureSpec.getSize(heightMeasureSpec)
33 | } else {
34 | var remainWidth = width
35 | (0 until childCount).map { getChildAt(it) }.forEach { child ->
36 | val lp = child.layoutParams as MarginLayoutParams
37 | if (isNewLine(lp, child, remainWidth, horizontalGap)) {
38 | remainWidth = width - child.measuredWidth
39 | height += (lp.topMargin + lp.bottomMargin + child.measuredHeight + verticalGap)
40 | } else {
41 | remainWidth -= child.measuredWidth
42 | if (height == 0) height =
43 | (lp.topMargin + lp.bottomMargin + child.measuredHeight + verticalGap)
44 | }
45 | remainWidth -= (lp.leftMargin + lp.rightMargin + horizontalGap)
46 | }
47 | }
48 |
49 | setMeasuredDimension(width, height)
50 | }
51 |
52 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
53 | var left = 0
54 | var top = 0
55 | var lastBottom = 0
56 | var count = 0
57 | (0 until childCount).map { getChildAt(it) }.forEach { child ->
58 | val lp = child.layoutParams as MarginLayoutParams
59 | if (isNewLine(lp, child, r - l - left, horizontalGap)) {
60 | left = -lp.leftMargin
61 | top = lastBottom
62 | lastBottom = 0
63 | }
64 | val childLeft = left + lp.leftMargin
65 | val childTop = top + lp.topMargin
66 | child.layout(
67 | childLeft,
68 | childTop,
69 | childLeft + child.measuredWidth,
70 | childTop + child.measuredHeight
71 | )
72 | if (lastBottom == 0) lastBottom = child.bottom + lp.bottomMargin + verticalGap
73 | left += child.measuredWidth + lp.leftMargin + lp.rightMargin + horizontalGap
74 | count++
75 | }
76 | }
77 |
78 | /**
79 | * place the [child] in a new line or not
80 | *
81 | * @param lp LayoutParams of [child]
82 | * @param child child view of [LineFeedLayout]
83 | * @param remainWidth the remain space of one line in [LineFeedLayout]
84 | * @param horizontalGap the horizontal gap for the children of [LineFeedLayout]
85 | */
86 | private fun isNewLine(
87 | lp: MarginLayoutParams,
88 | child: View,
89 | remainWidth: Int,
90 | horizontalGap: Int
91 | ): Boolean {
92 | val childOccupation = lp.leftMargin + child.measuredWidth + lp.rightMargin
93 | return (childOccupation + horizontalGap > remainWidth) && (childOccupation > remainWidth)
94 | }
95 | }
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/selector/AgeSelector.java:
--------------------------------------------------------------------------------
1 | package taylor.com.selector;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Color;
7 | import android.util.AttributeSet;
8 | import android.util.TypedValue;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.animation.AccelerateDecelerateInterpolator;
12 | import android.widget.ImageView;
13 | import android.widget.TextView;
14 |
15 | import taylor.com.selector2.Selector;
16 |
17 | public class AgeSelector extends Selector {
18 | private TextView tvTitle;
19 | private ImageView ivIcon;
20 | private ImageView ivSelector;
21 | private ValueAnimator valueAnimator;
22 | private String text;
23 | private int iconResId;
24 | private int indicatorResId;
25 | private int textColor;
26 | private int textSize;
27 |
28 | public AgeSelector(Context context) {
29 | super(context);
30 | }
31 |
32 | public AgeSelector(Context context, AttributeSet attrs) {
33 | super(context, attrs);
34 | }
35 |
36 | public AgeSelector(Context context, AttributeSet attrs, int defStyleAttr) {
37 | super(context, attrs, defStyleAttr);
38 | }
39 |
40 | private void onBindView(String text, int iconResId, int indicatorResId, int textColor, int textSize) {
41 | if (tvTitle != null) {
42 | tvTitle.setText(text);
43 | tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
44 | tvTitle.setTextColor(textColor);
45 | }
46 | if (ivIcon != null) {
47 | ivIcon.setImageResource(iconResId);
48 | }
49 | if (ivSelector != null) {
50 | ivSelector.setImageResource(indicatorResId);
51 | ivSelector.setAlpha(0);
52 | }
53 | }
54 |
55 | @Override
56 | public void onObtainAttrs(TypedArray typedArray) {
57 | text = typedArray.getString(R.styleable.Selector_text);
58 | iconResId = typedArray.getResourceId(R.styleable.Selector_img, 0);
59 | indicatorResId = typedArray.getResourceId(R.styleable.Selector_indicator, 0);
60 | textColor = typedArray.getColor(R.styleable.Selector_text_color, Color.parseColor("#FF222222"));
61 | textSize = typedArray.getInteger(R.styleable.Selector_text_size, 15);
62 | }
63 |
64 | @Override
65 | protected View onCreateView() {
66 | View view = LayoutInflater.from(this.getContext()).inflate(R.layout.age_selector, null);
67 | tvTitle = view.findViewById(R.id.tv_title);
68 | ivIcon = view.findViewById(R.id.iv_icon);
69 | ivSelector = view.findViewById(R.id.iv_selector);
70 | onBindView(text, iconResId, indicatorResId, textColor, textSize);
71 | return view;
72 | }
73 |
74 | @Override
75 | protected void onSwitchSelected(boolean isSelect) {
76 | if (isSelect) {
77 | playSelectedAnimation();
78 | } else {
79 | playUnselectedAnimation();
80 | }
81 | }
82 |
83 | private void playUnselectedAnimation() {
84 | if (ivSelector == null) {
85 | return;
86 | }
87 | if (valueAnimator != null) {
88 | valueAnimator.reverse();
89 | }
90 | }
91 |
92 | private void playSelectedAnimation() {
93 | if (ivSelector == null) {
94 | return;
95 | }
96 | valueAnimator = ValueAnimator.ofInt(0, 255);
97 | valueAnimator.setDuration(400);
98 | valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
99 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
100 | @Override
101 | public void onAnimationUpdate(ValueAnimator animation) {
102 | ivSelector.setAlpha((int) animation.getAnimatedValue());
103 | }
104 | });
105 | valueAnimator.start();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/selector/MainActivity.java:
--------------------------------------------------------------------------------
1 | package taylor.com.selector;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.Toast;
7 |
8 | import androidx.appcompat.app.AppCompatActivity;
9 |
10 | import java.util.ArrayList;
11 | import java.util.HashMap;
12 | import java.util.List;
13 |
14 | import taylor.com.selector2.Selector;
15 | import taylor.com.selector2.SelectorGroup;
16 |
17 | public class MainActivity extends AppCompatActivity {
18 | private List tags = new ArrayList<>();
19 | private HashMap orders = new HashMap<>();
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_main);
25 | initView();
26 | }
27 |
28 | private void initView() {
29 | //multiple-choice
30 | SelectorGroup multipleGroup = new SelectorGroup();
31 | multipleGroup.setChoiceMode(SelectorGroup.MODE_MULTIPLE_CHOICE);
32 | multipleGroup.setStateListener(new MultipleChoiceListener());
33 | ((Selector) findViewById(R.id.selector_10)).setGroup("multiple", multipleGroup);
34 | ((Selector) findViewById(R.id.selector_20)).setGroup("multiple", multipleGroup);
35 | ((Selector) findViewById(R.id.selector_30)).setGroup("multiple", multipleGroup);
36 |
37 | //single-choice
38 | SelectorGroup singleGroup = new SelectorGroup();
39 | singleGroup.setChoiceMode(SelectorGroup.MODE_SINGLE_CHOICE);
40 | singleGroup.setStateListener(new SingleChoiceListener());
41 | ((Selector) findViewById(R.id.single10)).setGroup("single", singleGroup);
42 | ((Selector) findViewById(R.id.single20)).setGroup("single", singleGroup);
43 | ((Selector) findViewById(R.id.single30)).setGroup("single", singleGroup);
44 |
45 | //order-choice
46 | SelectorGroup orderGroup = new SelectorGroup();
47 | orderGroup.setStateListener(new OrderChoiceListener());
48 | orderGroup.setChoiceMode(new OderChoiceMode());
49 | ((Selector) findViewById(R.id.selector_starters_duck)).setGroup("starters", orderGroup);
50 | ((Selector) findViewById(R.id.selector_starters_pork)).setGroup("starters", orderGroup);
51 | ((Selector) findViewById(R.id.selector_starters_springRoll)).setGroup("starters", orderGroup);
52 | ((Selector) findViewById(R.id.selector_main_pizza)).setGroup("main", orderGroup);
53 | ((Selector) findViewById(R.id.selector_main_pasta)).setGroup("main", orderGroup);
54 | ((Selector) findViewById(R.id.selector_soup_mushroom)).setGroup("soup", orderGroup);
55 | ((Selector) findViewById(R.id.selector_soup_scampi)).setGroup("soup", orderGroup);
56 | orderGroup.setSelected(true, (Selector) findViewById(R.id.selector_starters_duck));
57 |
58 | findViewById(R.id.btnKotlinStyle).setOnClickListener(new View.OnClickListener() {
59 | @Override
60 | public void onClick(View v) {
61 | Intent intent = new Intent(MainActivity.this, SelectorKtActivity.class);
62 | startActivity(intent);
63 | }
64 | });
65 | }
66 |
67 | /**
68 | * business logic for single-choice is here
69 | */
70 | private class SingleChoiceListener implements SelectorGroup.StateListener {
71 |
72 | @Override
73 | public void onStateChange(String groupTag, String tag, boolean isSelected) {
74 | Toast.makeText(MainActivity.this, tag + " is selected", Toast.LENGTH_SHORT).show();
75 | }
76 | }
77 |
78 | /**
79 | * business logic for multiple-choice is here
80 | */
81 | private class MultipleChoiceListener implements SelectorGroup.StateListener {
82 |
83 | @Override
84 | public void onStateChange(String groupTag, String tag, boolean isSelected) {
85 | if (isSelected) {
86 | tags.add(tag);
87 | } else {
88 | tags.remove(tag);
89 | }
90 | Toast.makeText(MainActivity.this, tags.toString() + " is selected", Toast.LENGTH_SHORT).show();
91 | }
92 | }
93 |
94 | /**
95 | * business logic for order-choice is here
96 | */
97 | private class OrderChoiceListener implements SelectorGroup.StateListener {
98 |
99 | @Override
100 | public void onStateChange(String groupTag, String tag, boolean isSelected) {
101 | if (isSelected) {
102 | orders.put(groupTag, tag);
103 | Toast.makeText(MainActivity.this, orders.toString() + " is selected", Toast.LENGTH_SHORT).show();
104 | }
105 | }
106 | }
107 |
108 | /**
109 | * extends the choice mode of SelectorGroup by implementing SelectorGroup.ChoiceAction
110 | * the new choice mode is like the behaviour when ordering by western food menu:one choice for one type
111 | */
112 | private class OderChoiceMode implements SelectorGroup.ChoiceAction {
113 |
114 | @Override
115 | public void onChoose(Selector selector, SelectorGroup selectorGroup, SelectorGroup.StateListener stateListener) {
116 | cancelPreSelector(selector, selectorGroup);
117 | selector.setSelected(true);
118 | if (stateListener != null) {
119 | stateListener.onStateChange(selector.getGroupTag(), selector.getSelectorTag(), true);
120 | }
121 | }
122 |
123 | private void cancelPreSelector(Selector selector, SelectorGroup selectorGroup) {
124 | Selector preSelector = selectorGroup.getPreSelector(selector.getGroupTag());
125 | if (preSelector != null) {
126 | preSelector.setSelected(false);
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/selector/OrderSelector.java:
--------------------------------------------------------------------------------
1 | package taylor.com.selector;
2 |
3 | import android.animation.AnimatorSet;
4 | import android.animation.ValueAnimator;
5 | import android.content.Context;
6 | import android.content.res.TypedArray;
7 | import android.graphics.Color;
8 | import android.util.AttributeSet;
9 | import android.util.TypedValue;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.animation.AccelerateDecelerateInterpolator;
13 | import android.view.animation.AnticipateOvershootInterpolator;
14 | import android.widget.ImageView;
15 | import android.widget.TextView;
16 |
17 | import taylor.com.selector2.Selector;
18 |
19 | /**
20 | * a Selector which shows an heart image on the right top when it is selected
21 | */
22 | public class OrderSelector extends Selector {
23 | private TextView tvTitle;
24 | private ImageView ivIcon;
25 | private ImageView ivSelector;
26 | private ValueAnimator alphaAnimator;
27 | private ValueAnimator scaleAnimator;
28 | private String text;
29 | private int iconResId;
30 | private int indicatorResId;
31 | private int textColor;
32 | private int textSize;
33 |
34 | public OrderSelector(Context context) {
35 | super(context);
36 | }
37 |
38 | public OrderSelector(Context context, AttributeSet attrs) {
39 | super(context, attrs);
40 | }
41 |
42 | public OrderSelector(Context context, AttributeSet attrs, int defStyleAttr) {
43 | super(context, attrs, defStyleAttr);
44 | }
45 |
46 | private void onBindView(String text, int iconResId, int indicatorResId, int textColor, int textSize) {
47 | if (tvTitle != null) {
48 | tvTitle.setText(text);
49 | tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
50 | tvTitle.setTextColor(textColor);
51 | }
52 | if (ivIcon != null) {
53 | ivIcon.setImageResource(iconResId);
54 | }
55 | if (ivSelector != null) {
56 | ivSelector.setImageResource(indicatorResId);
57 | ivSelector.setAlpha(0);
58 | }
59 | }
60 |
61 | @Override
62 | public void onObtainAttrs(TypedArray typedArray) {
63 | text = typedArray.getString(R.styleable.Selector_text);
64 | iconResId = typedArray.getResourceId(R.styleable.Selector_img, 0);
65 | indicatorResId = typedArray.getResourceId(R.styleable.Selector_indicator, 0);
66 | textColor = typedArray.getColor(R.styleable.Selector_text_color, Color.parseColor("#FF222222"));
67 | textSize = typedArray.getInteger(R.styleable.Selector_text_size, 15);
68 | }
69 |
70 | @Override
71 | protected View onCreateView() {
72 | View view = LayoutInflater.from(this.getContext()).inflate(R.layout.order_selector, null);
73 | tvTitle = view.findViewById(R.id.tv_title);
74 | ivIcon = view.findViewById(R.id.iv_icon);
75 | ivSelector = view.findViewById(R.id.iv_selector);
76 | onBindView(text, iconResId, indicatorResId, textColor, textSize);
77 | return view;
78 | }
79 |
80 | @Override
81 | protected void onSwitchSelected(boolean isSelect) {
82 | if (isSelect) {
83 | playSelectedAnimation();
84 | } else {
85 | playUnselectedAnimation();
86 | }
87 | }
88 |
89 | private void playUnselectedAnimation() {
90 | if (ivSelector == null) {
91 | return;
92 | }
93 | if (alphaAnimator != null) {
94 | alphaAnimator.reverse();
95 | }
96 | }
97 |
98 | private void playSelectedAnimation() {
99 | if (ivSelector == null) {
100 | return;
101 | }
102 | AnimatorSet set = new AnimatorSet();
103 | alphaAnimator = ValueAnimator.ofInt(0, 255);
104 | alphaAnimator.setDuration(200);
105 | alphaAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
106 | alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
107 | @Override
108 | public void onAnimationUpdate(ValueAnimator animation) {
109 | ivSelector.setAlpha((int) animation.getAnimatedValue());
110 | }
111 | });
112 |
113 | scaleAnimator = ValueAnimator.ofFloat(1f, 1.3f, 1f);
114 | scaleAnimator.setDuration(500);
115 | scaleAnimator.setInterpolator(new AnticipateOvershootInterpolator());
116 | scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
117 | @Override
118 | public void onAnimationUpdate(ValueAnimator animation) {
119 | ivSelector.setScaleX(((Float) animation.getAnimatedValue()));
120 | ivSelector.setScaleY(((Float) animation.getAnimatedValue()));
121 | }
122 | });
123 | set.playTogether(alphaAnimator, scaleAnimator);
124 | set.start();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/selector/SelectorKtActivity.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.selector
2 |
3 | import android.os.Bundle
4 | import android.support.constraint.ConstraintLayout
5 | import android.util.Log
6 | import android.view.View
7 | import android.widget.ImageView
8 | import android.widget.LinearLayout
9 | import android.widget.TextView
10 | import androidx.appcompat.app.AppCompatActivity
11 | import cn.neoclub.uki.home.game.GameDialogFragment
12 | import taylor.com.layout.*
13 | import taylor.com.slectorkt.Selector
14 | import taylor.com.slectorkt.SelectorGroup
15 | import java.io.Closeable
16 |
17 | class SelectorKtActivity : AppCompatActivity() {
18 |
19 | private val mans = listOf(
20 | Man(13, "teenager", R.drawable.teenage),
21 | Man(20, "man", R.drawable.man),
22 | Man(40, "old-man", R.drawable.old_man)
23 | )
24 |
25 | private lateinit var multipleModeContainer: LinearLayout
26 | private val key = object : Selector.Key {}
27 |
28 | /**
29 | * describe how age Selector looks like
30 | */
31 | private val ageSelectorView: ConstraintLayout
32 | get() = ConstraintLayout {
33 | layout_width = match_parent
34 | layout_height = match_parent
35 | ImageView {
36 | layout_id = "ivSelector"
37 | layout_width = 0
38 | layout_height = 30
39 | top_toTopOf = "ivContent"
40 | bottom_toBottomOf = "ivContent"
41 | start_toStartOf = "ivContent"
42 | end_toEndOf = "ivContent"
43 | background_res = R.drawable.age_selctor_shape
44 | alpha = 0f
45 | }
46 |
47 | ImageView {
48 | layout_id = "ivContent"
49 | layout_width = match_parent
50 | layout_height = 30
51 | center_horizontal = true
52 | src = R.drawable.man
53 | top_toTopOf = "ivSelector"
54 | }
55 |
56 | TextView {
57 | layout_id = "tvTitle"
58 | layout_width = match_parent
59 | layout_height = wrap_content
60 | bottom_toBottomOf = parent_id
61 | text = "man"
62 | gravity = gravity_center_horizontal
63 | }
64 | }
65 |
66 | /**
67 | * the listener for age selectors
68 | */
69 | private val onAgeSelectStateChange = { selector: Selector, select: Boolean ->
70 | selector.find("ivSelector")?.alpha = if (select) 1f else 0f
71 | }
72 |
73 | /**
74 | * the single choice mode controller for age selector
75 | */
76 | private val singleGroup = SelectorGroup().apply {
77 | choiceMode = SelectorGroup.MODE_SINGLE
78 | selectChangeListener = { selectors: List->
79 | // business logic here
80 | }
81 | }
82 |
83 | private val selectorBindAction = { selector: View, data: Any? ->
84 | selector.find("ivContent")?.setImageResource((data as Man).res)
85 | selector.find("tvTitle")?.text = (data as Man).title
86 | }
87 |
88 | /**
89 | * the multiple choice mode controller for age selector
90 | */
91 | private val multipleGroup = SelectorGroup().apply {
92 | choiceMode = SelectorGroup.MODE_MULTIPLE
93 | selectChangeListener = { selectors: List ->
94 | }
95 | }
96 |
97 | /**
98 | * create Selector non-dynamically just like in xml
99 | */
100 | private val rootView by lazy {
101 | ConstraintLayout {
102 | layout_width = match_parent
103 | layout_height = match_parent
104 |
105 | TextView {
106 | layout_id = "tvSingleMode"
107 | layout_width = wrap_content
108 | layout_height = wrap_content
109 | top_toTopOf = parent_id
110 | center_horizontal = true
111 | text = "single choice mode"
112 | textSize = 20f
113 | }
114 |
115 | Selector {
116 | layout_id = "sMan"
117 | tag = "man"
118 | group = singleGroup
119 | groupTag = "age"
120 | layout_width = 90
121 | layout_height = 50
122 | contentView = ageSelectorView
123 | onSelectChange = onAgeSelectStateChange
124 | top_toBottomOf = "tvSingleMode"
125 | center_horizontal = true
126 | bind = Binder(Man(20, "man", R.drawable.man), selectorBindAction)
127 | }
128 | Selector {
129 | layout_id = "sOldMan"
130 | tag = "old-man"
131 | group = singleGroup
132 | groupTag = "age"
133 | layout_width = 90
134 | layout_height = 50
135 | contentView = ageSelectorView
136 | onSelectChange = onAgeSelectStateChange
137 | top_toBottomOf = "sMan"
138 | start_toStartOf = parent_id
139 | end_toEndOf = parent_id
140 | horizontal_bias = 0.2f
141 | bind = Binder(Man(40, "old-man", R.drawable.old_man), selectorBindAction)
142 | }
143 | Selector {
144 | layout_id = "sTeenager"
145 | tag = "teenager"
146 | group = singleGroup
147 | groupTag = "age"
148 | layout_width = 90
149 | layout_height = 50
150 | contentView = ageSelectorView
151 | onSelectChange = onAgeSelectStateChange
152 | top_toBottomOf = "sMan"
153 | start_toStartOf = parent_id
154 | end_toEndOf = parent_id
155 | horizontal_bias = 0.8f
156 | bind = Binder(Man(13, "teenager", R.drawable.teenage), selectorBindAction)
157 | }
158 |
159 | TextView {
160 | layout_id = "tvMultiMode"
161 | layout_width = wrap_content
162 | layout_height = wrap_content
163 | top_toBottomOf = "sTeenager"
164 | center_horizontal = true
165 | text = "multiple choice mode"
166 | textSize = 20f
167 | }
168 |
169 | multipleModeContainer = LinearLayout {
170 | layout_id = "multiple-container"
171 | layout_width = wrap_content
172 | layout_height = wrap_content
173 | orientation = horizontal
174 | center_horizontal = true
175 | top_toBottomOf = "tvMultiMode"
176 | }
177 |
178 | Button {
179 | layout_width = match_parent
180 | layout_height = wrap_content
181 | text = "show game selector"
182 | top_toBottomOf = "multiple-container"
183 | onClick = {_->
184 | GameDialogFragment().show(supportFragmentManager,"game")
185 | }
186 | }
187 | }
188 | }
189 |
190 | override fun onCreate(savedInstanceState: Bundle?) {
191 | super.onCreate(savedInstanceState)
192 | setContentView(rootView)
193 | buildMultipleChoiceSelector(mans)
194 | }
195 |
196 | /**
197 | * build multiple choice [Selector] dynamically
198 | */
199 | private fun buildMultipleChoiceSelector(mans: List) {
200 | mans.forEach { man ->
201 | Selector {
202 | layout_id = man.title
203 | tag = man.title
204 | group = multipleGroup
205 | groupTag = "multiple-age"
206 | layout_width = 90
207 | layout_height = 50
208 | contentView = ageSelectorView
209 | onSelectChange = onAgeSelectStateChange
210 | this[key] = man
211 | bind = Binder(man){selector,data->
212 | find("ivContent")?.setImageResource((data as Man).res)
213 | find("tvTitle")?.text = (data as Man).title
214 | }
215 | }.also { multipleModeContainer.addView(it) }
216 | }
217 | }
218 | }
219 |
220 | /**
221 | * data bean for multiple choice [Selector]
222 | */
223 | data class Man(var age: Int, var title: String, var res: Int) : Closeable {
224 | override fun close() {
225 | age = -1
226 | title = ""
227 | res = -1
228 | }
229 | }
230 |
231 | /**
232 | * print collection bean in which you interested defined by [map]
233 | */
234 | fun Collection.print(map: (T) -> String) =
235 | StringBuilder("\n[").also { sb ->
236 | this.forEach { e -> sb.append("\n\t${map(e)},") }
237 | sb.append("\n]")
238 | }.toString()
--------------------------------------------------------------------------------
/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/age_selctor_shape.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/age_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_game_attr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_game_attr_select.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/duck.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/duck.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/game_selected.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_black.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/ic_close_black.webp
--------------------------------------------------------------------------------
/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/love.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/love.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/man.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/man.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/mushroom.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/mushroom.jpeg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/old_man.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/old_man.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/pasta.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/pasta.jpeg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/pizza.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/pizza.jpeg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/pork.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/pork.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/scampi.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/scampi.jpeg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/spring_roll.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/spring_roll.jpeg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/teenage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/drawable/teenage.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
22 |
23 |
38 |
39 |
56 |
57 |
74 |
75 |
76 |
77 |
88 |
89 |
104 |
105 |
122 |
123 |
140 |
141 |
142 |
153 |
154 |
165 |
166 |
182 |
183 |
200 |
201 |
218 |
219 |
230 |
231 |
242 |
243 |
259 |
260 |
277 |
278 |
289 |
290 |
301 |
302 |
318 |
319 |
336 |
337 |
344 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/age_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
27 |
28 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/order_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
19 |
20 |
30 |
31 |
42 |
43 |
--------------------------------------------------------------------------------
/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/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/man.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-hdpi/man.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/old_man.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-hdpi/old_man.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/teenage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-hdpi/teenage.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Selector
3 |
4 | man
5 | old man
6 | teenager
7 | single man
8 | single old man
9 | single teenager
10 |
11 | starters_pork
12 | starters_duck
13 | starters_springRoll
14 | main_pizza
15 | main_pasta
16 | soup_mushroom
17 | soup_scampi
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/taylor/com/selector/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package taylor.com.selector;
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 | ext.kotlin_version = '1.3.21'
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.2.1'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 |
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Selector/6918fea870e2efda31c02df0f87d26555d8dfe7e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/selector2/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/selector2/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'kotlin-kapt'
5 | android {
6 | compileSdkVersion 28
7 |
8 |
9 |
10 | defaultConfig {
11 | minSdkVersion 15
12 | targetSdkVersion 28
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
17 |
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 |
27 | }
28 |
29 | dependencies {
30 | implementation fileTree(dir: 'libs', include: ['*.jar'])
31 | testImplementation 'junit:junit:4.12'
32 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
34 | //kotlin
35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
36 | }
37 |
--------------------------------------------------------------------------------
/selector2/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 |
--------------------------------------------------------------------------------
/selector2/src/androidTest/java/taylor/com/selector2/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package taylor.com.selector2;
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("taylor.com.selector2.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/selector2/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/selector2/src/main/java/taylor/com/selector2/Selector.java:
--------------------------------------------------------------------------------
1 | package taylor.com.selector2;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Color;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.FrameLayout;
10 |
11 | /**
12 | * it is a customized view acts like a checkbox.
13 | * it can be selected or unselected, the background will change accordingly. wrapping this business logic into a single view for clean code in Fragment
14 | */
15 | public abstract class Selector extends FrameLayout implements View.OnClickListener {
16 | /**
17 | * the unique tag for a selector
18 | */
19 | private String tag;
20 | /**
21 | * the tag indicates which group this selector belongs to,
22 | * set the same group tag for selectors which want single choice mode
23 | */
24 | private String groupTag;
25 | /**
26 | * the group which this Selector belongs to
27 | */
28 | private SelectorGroup selectorGroup;
29 |
30 | public Selector(Context context) {
31 | super(context);
32 | initView(context, null);
33 | }
34 |
35 | public Selector(Context context, AttributeSet attrs) {
36 | super(context, attrs);
37 | initView(context, attrs);
38 | }
39 |
40 | public Selector(Context context, AttributeSet attrs, int defStyleAttr) {
41 | super(context, attrs, defStyleAttr);
42 | initView(context, attrs);
43 | }
44 |
45 | private void initView(Context context, AttributeSet attrs) {
46 | //read declared attributes
47 | if (attrs != null) {
48 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Selector);
49 | int tagResId = typedArray.getResourceId(R.styleable.Selector_tag, 0);
50 | tag = context.getString(tagResId);
51 | onObtainAttrs(typedArray);
52 | typedArray.recycle();
53 | } else {
54 | tag = "default tag";
55 | }
56 | //inflate views
57 | View view = onCreateView();
58 | LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
59 | this.addView(view, params);
60 | this.setOnClickListener(this);
61 |
62 | }
63 |
64 | public void onObtainAttrs(TypedArray typedArray) {
65 | }
66 |
67 | /**
68 | * add this Selector into a SelectorGroup
69 | *
70 | * @param selectorGroup
71 | * @return
72 | */
73 | public Selector setGroup(String groupTag, SelectorGroup selectorGroup) {
74 | this.selectorGroup = selectorGroup;
75 | this.groupTag = groupTag;
76 | return this;
77 | }
78 |
79 | public String getGroupTag(){
80 | return groupTag;
81 | }
82 |
83 | /**
84 | * design how the selector looks like
85 | *
86 | * @return
87 | */
88 | protected abstract View onCreateView();
89 |
90 | public String getSelectorTag() {
91 | return tag;
92 | }
93 |
94 | public void setSelectorTag(String tag) {
95 | this.tag = tag;
96 | }
97 |
98 | @Override
99 | public void setSelected(boolean selected) {
100 | boolean isPreSelected = isSelected();
101 | super.setSelected(selected);
102 | if (isPreSelected != selected) {
103 | onSwitchSelected(selected);
104 | }
105 | }
106 |
107 | @Override
108 | public void onClick(View v) {
109 | //deliver the click event to the SelectorGroup
110 | if (selectorGroup != null) {
111 | selectorGroup.onSelectorClick(this);
112 | }
113 | }
114 |
115 | /**
116 | * it will be invoked when select state changes
117 | *
118 | * @param isSelect
119 | */
120 | protected abstract void onSwitchSelected(boolean isSelect);
121 | }
122 |
--------------------------------------------------------------------------------
/selector2/src/main/java/taylor/com/selector2/SelectorGroup.java:
--------------------------------------------------------------------------------
1 | package taylor.com.selector2;
2 |
3 | import java.util.HashMap;
4 |
5 | /**
6 | * it controls the states between several choices which is a {@link Selector},
7 | * there are two modes by default: {@link #MODE_SINGLE_CHOICE} act as RadioButton + RadioGroup ,{@link #MODE_MULTIPLE_CHOICE } act as CheckBox
8 | * the advantage of this class is it don't need to be the parent view of several choices, thus you could place the choices whatever your like
9 | * and choice mode could be extends by implementing {#link #ChoiceAction} interface
10 | */
11 | public class SelectorGroup {
12 | public static final int MODE_SINGLE_CHOICE = 1;
13 | public static final int MODE_MULTIPLE_CHOICE = 2;
14 |
15 | private ChoiceAction choiceMode;
16 | private StateListener onStateChangeListener;
17 | /**
18 | * a map to keep previous selected selector
19 | */
20 | private HashMap selectorMap = new HashMap<>();
21 |
22 | /**
23 | * customized an choice mode by yourself
24 | *
25 | * @param choiceMode
26 | */
27 | public void setChoiceMode(ChoiceAction choiceMode) {
28 | this.choiceMode = choiceMode;
29 | }
30 |
31 | /**
32 | * set a default choice mode
33 | *
34 | * @param mode
35 | */
36 | public void setChoiceMode(int mode) {
37 | switch (mode) {
38 | case MODE_MULTIPLE_CHOICE:
39 | choiceMode = new MultipleAction();
40 | break;
41 | case MODE_SINGLE_CHOICE:
42 | choiceMode = new SingleAction();
43 | break;
44 | }
45 | }
46 |
47 | public void setStateListener(StateListener onStateChangeListener) {
48 | this.onStateChangeListener = onStateChangeListener;
49 | }
50 |
51 | /**
52 | * get the selector which clicked last time by the specific group tag
53 | *
54 | * @param groupTag a tag which the previous selector belongs to
55 | * @return
56 | */
57 | public Selector getPreSelector(String groupTag) {
58 | return selectorMap.get(groupTag);
59 | }
60 |
61 | /**
62 | * toggle or cancel one choice
63 | *
64 | * @param selected
65 | * @param selector
66 | */
67 | public void setSelected(boolean selected, Selector selector) {
68 | if (selector == null) {
69 | return;
70 | }
71 | if (selected) {
72 | //keep click selector in map
73 | selectorMap.put(selector.getGroupTag(), selector);
74 | }
75 | selector.setSelected(selected);
76 | if (onStateChangeListener != null) {
77 | onStateChangeListener.onStateChange(selector.getGroupTag(), selector.getSelectorTag(), selected);
78 | }
79 | }
80 |
81 | /**
82 | * cancel selected state of one Selector when another is selected
83 | *
84 | * @param selector the Selector which is selected right now
85 | */
86 | private void cancelPreSelector(Selector selector) {
87 | String groupTag = selector.getGroupTag();
88 | Selector preSelector = getPreSelector(groupTag);
89 | if (preSelector != null) {
90 | preSelector.setSelected(false);
91 | }
92 | }
93 |
94 | /**
95 | * add extra layer which means more complex
96 | *
97 | * @param selector
98 | */
99 | void onSelectorClick(Selector selector) {
100 | if (choiceMode != null) {
101 | choiceMode.onChoose(selector, this, onStateChangeListener);
102 | }
103 | //keep click selector in map
104 | selectorMap.put(selector.getGroupTag(), selector);
105 | }
106 |
107 | public void clear() {
108 | if (selectorMap != null) {
109 | selectorMap.clear();
110 | }
111 | }
112 |
113 | public interface ChoiceAction {
114 | /**
115 | * invoked when one selector is clicked
116 | *
117 | * @param selector the clicked selector
118 | * @param selectorGroup
119 | * @param stateListener
120 | */
121 | void onChoose(Selector selector, SelectorGroup selectorGroup, StateListener stateListener);
122 | }
123 |
124 | /**
125 | * pre-defined choice mode: previous choice will be canceled if there is a new choice
126 | */
127 | private class SingleAction implements ChoiceAction {
128 |
129 | @Override
130 | public void onChoose(Selector selector, SelectorGroup selectorGroup, StateListener stateListener) {
131 | cancelPreSelector(selector);
132 | setSelected(true, selector);
133 | }
134 | }
135 |
136 | /**
137 | * pre-defined choice mode: all choices will be preserved
138 | */
139 | private class MultipleAction implements ChoiceAction {
140 |
141 | @Override
142 | public void onChoose(Selector selector, SelectorGroup selectorGroup, StateListener stateListener) {
143 | boolean isSelected = selector.isSelected();
144 | setSelected(!isSelected, selector);
145 | }
146 | }
147 |
148 | public interface StateListener {
149 | void onStateChange(String groupTag, String tag, boolean isSelected);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/selector2/src/main/java/taylor/com/slectorkt/Selector.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.slectorkt
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import android.view.ViewGroup.LayoutParams.MATCH_PARENT
7 | import android.widget.FrameLayout
8 | import java.io.Closeable
9 | import java.util.*
10 | import kotlin.collections.HashMap
11 |
12 | /**
13 | * a ViewGroup that has customized action when selected or unselected, it could be an substitution for [android.widget.CheckBox] and [android.widget.RadioButton]
14 | * [contentView] describe how do [Selector] looks like,
15 | * [onSelectChange] describe what effect will be shown after selection state change,
16 | * [tags] keeps data bean for this [Selector],
17 | * [group] describe selection mode for [Selector], and the mode could be extended easily.
18 | */
19 | class Selector @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
20 | FrameLayout(context, attrs, defStyleAttr) {
21 |
22 | /**
23 | * the values this [Selector] carry
24 | */
25 | var tags = HashMap()
26 |
27 | /**
28 | * the unique identifier for a [Selector]
29 | */
30 | var tag: String = "default tag-${UUID.randomUUID()}"
31 |
32 | /**
33 | * the identifier for the [SelectorGroup] this [Selector] belongs to
34 | */
35 | var groupTag: String = "default group tag"
36 |
37 | /**
38 | * the [SelectorGroup] this [Selector] belongs to
39 | */
40 | var group: SelectorGroup? = null
41 |
42 | /**
43 | * the layout view for this [Selector]
44 | */
45 | var contentView: View? = null
46 | set(value) {
47 | field = value
48 | value?.let {
49 | addView(it, LayoutParams(MATCH_PARENT, MATCH_PARENT))
50 | setOnClickListener {
51 | group?.onSelectorClick(this)
52 | }
53 | }
54 | }
55 |
56 | /**
57 | * it will be invoked when the selection state of this [Selector] has changed,
58 | * override it if you want customized effect of selected or unselected
59 | */
60 | var onSelectChange: ((Selector, Boolean) -> Unit)? = null
61 |
62 | /**
63 | * whether this [Selector] is selected
64 | */
65 | var isSelecting: Boolean = false
66 |
67 | init {
68 | contentView?.let {
69 | addView(it, LayoutParams(MATCH_PARENT, MATCH_PARENT))
70 | setOnClickListener {
71 | group?.onSelectorClick(this)
72 | }
73 | }
74 | }
75 |
76 | operator fun set(key: Key, closeable: Closeable) {
77 | tags[key] = closeable
78 | }
79 |
80 | operator fun get(key: Key): T? = (tags.getOrElse(key, { null })) as T
81 |
82 | fun showSelectEffect(select: Boolean) {
83 | if (isSelecting != select) {
84 | onSelectChange?.invoke(this, select)
85 | }
86 | isSelecting = select
87 | }
88 |
89 | override fun onDetachedFromWindow() {
90 | super.onDetachedFromWindow()
91 | clear()
92 | }
93 |
94 | /**
95 | * clear the values attached to this [Selector]
96 | */
97 | private fun clear() {
98 | group?.clear(this)
99 | tags.forEach { entry ->
100 | closeWithException(entry.value)
101 | }
102 | }
103 |
104 | private fun closeWithException(closable: Closeable?) {
105 | try {
106 | closable?.close()
107 | } catch (e: Exception) {
108 | }
109 | }
110 |
111 | override fun hashCode(): Int {
112 | return if (tag.isNullOrEmpty()) "default".hashCode() else tag.hashCode()
113 | }
114 |
115 | /**
116 | * the key for data bean of this [Selector]
117 | */
118 | interface Key
119 | }
120 |
121 |
--------------------------------------------------------------------------------
/selector2/src/main/java/taylor/com/slectorkt/SelectorGroup.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.slectorkt
2 |
3 | import java.io.Closeable
4 |
5 | /**
6 | * the controller for [Selector]s
7 | */
8 | class SelectorGroup {
9 | companion object {
10 | /**
11 | * single choice mode, previous [Selector] will be unselected if a new one is selected
12 | */
13 | var MODE_SINGLE = { selectorGroup: SelectorGroup, selector: Selector, map: LinkedHashMap> ->
14 | selectorGroup.run {
15 | findLast(selector.groupTag)?.let { setSelected(it, false) }
16 | setSelected(selector, true)
17 | }
18 | }
19 |
20 | /**
21 | * multiple choice mode, several [Selector] could be selected in one [SelectorGroup]
22 | */
23 | var MODE_MULTIPLE = { selectorGroup: SelectorGroup, selector: Selector, map: LinkedHashMap> ->
24 | selectorGroup.setSelected(selector, ! selector.isSelecting)
25 | }
26 | }
27 |
28 | /**
29 | * the selected [Selector]
30 | * key is group tag and value is selected [Selector] in this group
31 | * the reason why use [LinkedHashMap] is to keep the sequence of groups
32 | */
33 | private var selectorMap = LinkedHashMap>()
34 |
35 | /**
36 | * the selected tag of [Selector]
37 | */
38 | private var selectTagMap = LinkedHashMap>()
39 |
40 | /**
41 | * the selected data of [Selector], which is kept in [Selector.tags]
42 | */
43 | private var selectDataMap = LinkedHashMap>()
44 |
45 | /**
46 | * the choice mode of this [SelectorGroup], there are two default choice mode, which is [MODE_SINGLE] and [MODE_MULTIPLE]
47 | */
48 | var choiceMode: ((SelectorGroup, Selector, map: LinkedHashMap>) -> Unit)? = null
49 |
50 | /**
51 | * if selection in this [SelectorGroup] is changed ,this lambda will be invoked,
52 | * override this to listen the change of selection, [Selector] will be delivered to business layer
53 | */
54 | var selectChangeListener: ((List/*selected set*/) -> Unit)? = null
55 |
56 | /**
57 | * if selection in this [SelectorGroup] is changed ,this lambda will be invoked,
58 | * override this to listen the change of selection, [Selector.tag] will be delivered to business layer
59 | */
60 | var selectTagChangeListener: ((List) -> Unit)? = null
61 |
62 | /**
63 | * if selection in this [SelectorGroup] is changed ,this lambda will be invoked,
64 | * override this to listen the change of selection, [Selector.tags] will be delivered to business layer
65 | */
66 | private var selectDataChangeListener: ((List) -> Unit)? = null
67 |
68 | /**
69 | * the key for getting the data of selected [Selector]
70 | */
71 | private var key: Selector.Key<*>? = null
72 |
73 | fun setSelectDataChangeListener(key: Selector.Key, listener: (List) -> Unit) {
74 | this.key = key
75 | selectDataChangeListener = listener
76 | }
77 |
78 | fun onSelectorClick(selector: Selector) {
79 | choiceMode?.invoke(this, selector, selectorMap)
80 | }
81 |
82 | /**
83 | * find [Selector]s with the same [groupTag]
84 | */
85 | fun find(groupTag: String) = selectorMap[groupTag]
86 |
87 | /**
88 | * find last selected [Selector] of [groupTag]
89 | */
90 | fun findLast(groupTag: String) = find(groupTag)?.takeUnless { it.isNullOrEmpty() }?.last()
91 |
92 | fun setSelected(selector: Selector, select: Boolean) {
93 | if (select) {
94 | selectTagMap[selector.groupTag]?.also { it.add(selector.tag) } ?: kotlin.run { selectTagMap[selector.groupTag] = mutableSetOf(selector.tag) }
95 | selectorMap[selector.groupTag]?.also { it.add(selector) } ?: kotlin.run { selectorMap[selector.groupTag] = mutableSetOf(selector) }
96 | selectDataMap[selector.groupTag]?.also { set -> selector.tags[key]?.let { set.add(it) } } ?: kotlin.run {
97 | selector.tags[key]?.let {
98 | selectDataMap[selector.groupTag] = mutableSetOf(it)
99 | }
100 | }
101 | } else {
102 | selectTagMap[selector.groupTag]?.also { it.remove(selector.tag) }
103 | selectorMap[selector.groupTag]?.also { it.remove(selector) }
104 | selectDataMap[selector.groupTag]?.also { it.remove(selector.tags[key]) }
105 | }
106 | selector.showSelectEffect(select)
107 | selectChangeListener?.invoke(selectorMap.flatMap { it.value })
108 | selectTagChangeListener?.invoke(selectTagMap.flatMap { it.value })
109 | selectDataChangeListener?.invoke(selectDataMap.flatMap { it.value })
110 | }
111 |
112 | fun clear(selector:Selector) {
113 | selectorMap[selector.groupTag]?.remove(selector)
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/selector2/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/selector2/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Selector
3 |
4 |
--------------------------------------------------------------------------------
/selector2/src/test/java/taylor/com/selector2/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package taylor.com.selector2;
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 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':selector2'
2 |
--------------------------------------------------------------------------------