├── .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 |