├── .gitignore
├── .idea
├── .name
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── taylor
│ │ └── com
│ │ └── ui
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── taylor
│ │ │ └── com
│ │ │ ├── adapter
│ │ │ ├── MyAdapter.kt
│ │ │ └── MyViewHolder.kt
│ │ │ ├── bean
│ │ │ ├── ColorBean.kt
│ │ │ └── User.kt
│ │ │ ├── dsl
│ │ │ ├── Layout.kt
│ │ │ ├── LayoutBuilder.kt
│ │ │ ├── LayoutConstants.kt
│ │ │ ├── LayoutExtra.kt
│ │ │ ├── LayoutHelperFun.kt
│ │ │ ├── SelectorBuilder.kt
│ │ │ └── ShapeBuilder.kt
│ │ │ ├── ui
│ │ │ ├── FirstFragment.kt
│ │ │ └── MainActivity.kt
│ │ │ └── views
│ │ │ ├── LineFeedLayout.kt
│ │ │ ├── OneViewGroup.kt
│ │ │ ├── PercentLayout.kt
│ │ │ ├── ProgressBar.kt
│ │ │ ├── Selector.kt
│ │ │ └── SelectorGroup.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── bg_orange_btn.xml
│ │ ├── diamond_tag.webp
│ │ ├── ic_back_black.webp
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_member_more.png
│ │ ├── tag_checked_shape.xml
│ │ └── user_portrait_gender_female.png
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── 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
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── taylor
│ └── com
│ └── ui
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Layout_dsl
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | xmlns:android
17 |
18 | ^$
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | xmlns:.*
28 |
29 | ^$
30 |
31 |
32 | BY_NAME
33 |
34 |
35 |
36 |
37 |
38 |
39 | .*:id
40 |
41 | http://schemas.android.com/apk/res/android
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | .*:name
51 |
52 | http://schemas.android.com/apk/res/android
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | name
62 |
63 | ^$
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | style
73 |
74 | ^$
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | .*
84 |
85 | ^$
86 |
87 |
88 | BY_NAME
89 |
90 |
91 |
92 |
93 |
94 |
95 | .*
96 |
97 | http://schemas.android.com/apk/res/android
98 |
99 |
100 | ANDROID_ATTRIBUTE_ORDER
101 |
102 |
103 |
104 |
105 |
106 |
107 | .*
108 |
109 | .*
110 |
111 |
112 | BY_NAME
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 1.8
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Layout_DSL
2 | Build Android layout dynamically with kotlin, get rid of xml file, which has poor performance
3 |
4 |
5 | ```kotlin
6 | class MainActivity : AppCompatActivity() {
7 | // build layout dynamically by DSL
8 | private val contentView by lazy {
9 | ConstraintLayout {
10 | layout_width = match_parent
11 | layout_height = match_parent
12 |
13 | TextView {
14 | layout_width = wrap_content
15 | layout_height = wrap_content
16 | text = "commit"
17 | textSize = 30f
18 | gravity = gravity_center
19 | center_horizontal = true
20 | top_toTopOf = parent_id
21 | padding = 10
22 | }
23 | }
24 | }
25 |
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | super.onCreate(savedInstanceState)
28 | // set layout to content view
29 | setContentView(contentView)
30 | }
31 | }
32 | ```
33 |
--------------------------------------------------------------------------------
/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 |
5 | android {
6 | compileSdkVersion 29
7 | buildToolsVersion "29.0.2"
8 |
9 | defaultConfig {
10 | applicationId "taylor.com.layout_dsl"
11 | minSdkVersion 17
12 | targetSdkVersion 29
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | // To inline the bytecode built with JVM target 1.8 into
27 | // bytecode that is being built with JVM target 1.6. (e.g. navArgs)
28 |
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 | kotlinOptions {
35 | jvmTarget = "1.8"
36 | }
37 |
38 | }
39 |
40 | dependencies {
41 | implementation fileTree(dir: 'libs', include: ['*.jar'])
42 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
43 | implementation 'androidx.appcompat:appcompat:1.1.0'
44 | implementation 'androidx.core:core-ktx:1.2.0'
45 | implementation 'com.google.android.material:material:1.1.0'
46 | implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
47 | implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
48 | testImplementation 'junit:junit:4.12'
49 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
51 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
52 | //Glide
53 | implementation 'com.github.bumptech.glide:glide:3.7.0'
54 | implementation 'jp.wasabeef:glide-transformations:2.0.0'
55 | implementation 'androidx.viewpager2:viewpager2:1.0.0'
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/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/ui/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.ui
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("taylor.com.layout_dsl", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/adapter/MyAdapter.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.adapter
2 |
3 | import android.view.ViewGroup
4 | import androidx.recyclerview.widget.RecyclerView
5 | import taylor.com.bean.User
6 | import taylor.com.dsl.*
7 |
8 | /**
9 | * show how use layout dsl in [RecyclerView.Adapter]
10 | */
11 | class MyAdapter(var myBean: List?) : RecyclerView.Adapter() {
12 |
13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
14 | val itemView = parent.context.run {
15 | ConstraintLayout {
16 | layout_height = 90
17 | layout_width = match_parent
18 | margin_end = 20
19 | margin_start =20
20 | background_color = "#eeeeee"
21 |
22 | TextView {
23 | layout_id = "tvContent"
24 | layout_width = match_parent
25 | layout_height = wrap_content
26 | textSize = 15f
27 | gravity = gravity_center
28 | start_toStartOf = parent_id
29 | top_toTopOf = parent_id
30 | }
31 |
32 | View {
33 | layout_id = "vDivider"
34 | layout_width = match_parent
35 | layout_height = 1
36 | top_toBottomOf = "tvContent"
37 | background_color = "#888888"
38 | }
39 |
40 | TextView {
41 | layout_id = "tvStart"
42 | layout_width = wrap_content
43 | layout_height = wrap_content
44 | textSize = 26f
45 | textColor ="#3F4658"
46 | text = "start"
47 | start_toStartOf = parent_id
48 | top_toBottomOf = "vDivider"
49 | margin_top = 20
50 | }
51 |
52 | TextView {
53 | layout_id = "tvEnd"
54 | layout_width = wrap_content
55 | layout_height = wrap_content
56 | textSize = 26f
57 | textColor ="#3F4658"
58 | text = "end"
59 | end_toEndOf = parent_id
60 | align_vertical_to = "tvStart"
61 | }
62 |
63 |
64 | }
65 | }
66 | return MyViewHolder(itemView)
67 | }
68 |
69 | override fun getItemCount(): Int {
70 | return myBean?.size ?: 0
71 | }
72 |
73 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
74 | myBean?.get(position)?.let { holder.bind(it) }
75 | }
76 | }
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/adapter/MyViewHolder.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.adapter
2 |
3 | import android.graphics.Color
4 | import android.view.View
5 | import android.widget.TextView
6 | import androidx.recyclerview.widget.RecyclerView
7 | import taylor.com.bean.User
8 | import taylor.com.dsl.find
9 |
10 | class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
11 |
12 | fun bind(user: User?) {
13 | itemView.apply {
14 | find("tvContent")?.apply {
15 | text = user?.name ?: "no name"
16 | val color = if (user?.gender == 1) Color.parseColor("#b300ff00") else Color.parseColor("#b3ff00ff")
17 | setBackgroundColor(color)
18 | }
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/bean/ColorBean.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.bean
2 |
3 | data class ColorBean(val color:String?)
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/bean/User.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.bean
2 |
3 | data class User (var name:String,var age:Int,var gender:Int = 0)
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/dsl/Layout.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.dsl
2 |
3 | import android.content.res.Resources
4 | import android.graphics.Bitmap
5 | import android.graphics.Color
6 | import android.graphics.drawable.Drawable
7 | import android.graphics.drawable.GradientDrawable
8 | import android.graphics.drawable.StateListDrawable
9 | import android.text.Editable
10 | import android.text.InputFilter
11 | import android.text.InputFilter.LengthFilter
12 | import android.view.KeyEvent
13 | import android.view.View
14 | import android.view.ViewGroup
15 | import android.widget.*
16 | import androidx.appcompat.content.res.AppCompatResources
17 | import androidx.constraintlayout.helper.widget.Flow
18 | import androidx.constraintlayout.widget.*
19 | import androidx.core.content.ContextCompat
20 | import androidx.core.content.res.ResourcesCompat
21 | import androidx.core.view.MarginLayoutParamsCompat
22 | import androidx.core.widget.NestedScrollView
23 | import androidx.recyclerview.widget.RecyclerView
24 | import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
25 |
26 | //
27 | inline var View.layout_id: String
28 | get() {
29 | return ""
30 | }
31 | set(value) {
32 | id = value.toLayoutId()
33 | }
34 | inline var View.padding_top: Number
35 | get() {
36 | return 0
37 | }
38 | set(value) {
39 | setPadding(paddingLeft, value.dp, paddingRight, paddingBottom)
40 | }
41 |
42 | inline var View.padding_bottom: Number
43 | get() {
44 | return 0
45 | }
46 | set(value) {
47 | setPadding(paddingLeft, paddingTop, paddingRight, value.dp)
48 | }
49 |
50 | inline var View.padding_start: Number
51 | get() {
52 | return 0
53 | }
54 | set(value) {
55 | setPadding(value.dp, paddingTop, paddingRight, paddingBottom)
56 | }
57 |
58 | inline var View.padding_end: Number
59 | get() {
60 | return 0
61 | }
62 | set(value) {
63 | setPadding(paddingLeft, paddingTop, value.dp, paddingBottom)
64 | }
65 |
66 | inline var View.padding: Number
67 | get() {
68 | return 0
69 | }
70 | set(value) {
71 | setPadding(value.dp, value.dp, value.dp, value.dp)
72 | }
73 |
74 | inline var View.padding_horizontal: Number
75 | get() {
76 | return 0
77 | }
78 | set(value) {
79 | padding_start = value
80 | padding_end = value
81 | }
82 |
83 | inline var View.padding_vertical: Number
84 | get() {
85 | return 0
86 | }
87 | set(value) {
88 | padding_top = value
89 | padding_bottom = value
90 | }
91 |
92 | inline var View.layout_width: Number
93 | get() {
94 | return 0
95 | }
96 | set(value) {
97 | val w = if (value.dp > 0) value.dp else value.toInt()
98 | val h = layoutParams?.height ?: 0
99 | updateLayoutParams {
100 | width = w
101 | height = h
102 | }
103 | }
104 |
105 | inline var View.layout_height: Number
106 | get() {
107 | return 0
108 | }
109 | set(value) {
110 | val w = layoutParams?.width ?: 0
111 | val h = if (value.dp > 0) value.dp else value.toInt()
112 | updateLayoutParams {
113 | width = w
114 | height = h
115 | }
116 | }
117 |
118 | inline var View.alignParentStart: Boolean
119 | get() {
120 | return false
121 | }
122 | set(value) {
123 | if (!value) return
124 | layoutParams = RelativeLayout.LayoutParams(layoutParams.width, layoutParams.height).apply {
125 | (layoutParams as? RelativeLayout.LayoutParams)?.rules?.forEachIndexed { index, i ->
126 | addRule(index, i)
127 | }
128 | addRule(RelativeLayout.ALIGN_PARENT_START, RelativeLayout.TRUE)
129 | }
130 | }
131 |
132 | inline var View.alignParentEnd: Boolean
133 | get() {
134 | return false
135 | }
136 | set(value) {
137 | if (!value) return
138 | layoutParams = RelativeLayout.LayoutParams(layoutParams.width, layoutParams.height).apply {
139 | (layoutParams as? RelativeLayout.LayoutParams)?.rules?.forEachIndexed { index, i ->
140 | addRule(index, i)
141 | }
142 | addRule(RelativeLayout.ALIGN_PARENT_END, RelativeLayout.TRUE)
143 | }
144 | }
145 |
146 | inline var View.centerVertical: Boolean
147 | get() {
148 | return false
149 | }
150 | set(value) {
151 | if (!value) return
152 | layoutParams = RelativeLayout.LayoutParams(layoutParams.width, layoutParams.height).apply {
153 | (layoutParams as? RelativeLayout.LayoutParams)?.rules?.forEachIndexed { index, i ->
154 | addRule(index, i)
155 | }
156 | addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE)
157 | }
158 | }
159 |
160 | inline var View.centerInParent: Boolean
161 | get() {
162 | return false
163 | }
164 | set(value) {
165 | if (!value) return
166 | layoutParams = RelativeLayout.LayoutParams(layoutParams.width, layoutParams.height).apply {
167 | (layoutParams as? RelativeLayout.LayoutParams)?.rules?.forEachIndexed { index, i ->
168 | addRule(index, i)
169 | }
170 | addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE)
171 | }
172 | }
173 |
174 | inline var View.weight: Float
175 | get() {
176 | return 0f
177 | }
178 | set(value) {
179 | updateLayoutParams {
180 | gravity = (layoutParams as? LinearLayout.LayoutParams)?.gravity ?: -1
181 | weight = value
182 | }
183 | }
184 | inline var View.layout_gravity: Int
185 | get() {
186 | return -1
187 | }
188 | set(value) {
189 | updateLayoutParams {
190 | weight = (layoutParams as? LinearLayout.LayoutParams)?.weight ?: 0f
191 | gravity = value
192 | }
193 | }
194 |
195 | inline var View.toCircleOf: String
196 | get() {
197 | return ""
198 | }
199 | set(value) {
200 | updateLayoutParams {
201 | circleConstraint = value.toLayoutId()
202 | }
203 | }
204 |
205 | inline var View.circle_radius: Int
206 | get() {
207 | return -1
208 | }
209 | set(value) {
210 | updateLayoutParams {
211 | circleRadius = value.dp
212 | }
213 | }
214 |
215 | inline var View.circle_angle: Float
216 | get() {
217 | return -1f
218 | }
219 | set(value) {
220 | updateLayoutParams {
221 | circleAngle = value
222 | }
223 | }
224 |
225 | inline var View.start_toStartOf: String
226 | get() {
227 | return ""
228 | }
229 | set(value) {
230 | updateLayoutParams {
231 | startToStart = value.toLayoutId()
232 | startToEnd = -1
233 | }
234 | }
235 |
236 | inline var View.start_toStartViewOf: View?
237 | get() {
238 | return null
239 | }
240 | set(value) {
241 | updateLayoutParams {
242 | startToStart = value?.id ?: -1
243 | startToEnd = -1
244 | }
245 | }
246 |
247 | inline var View.start_toEndOf: String
248 | get() {
249 | return ""
250 | }
251 | set(value) {
252 | updateLayoutParams {
253 | startToEnd = value.toLayoutId()
254 | startToStart = -1
255 | }
256 | }
257 |
258 | inline var View.start_toEndViewOf: View?
259 | get() {
260 | return null
261 | }
262 | set(value) {
263 | updateLayoutParams {
264 | startToEnd = value?.id ?: -1
265 | startToStart = -1
266 | }
267 | }
268 |
269 | inline var View.top_toBottomOf: String
270 | get() {
271 | return ""
272 | }
273 | set(value) {
274 | updateLayoutParams {
275 | topToBottom = value.toLayoutId()
276 | topToTop = -1
277 | }
278 | }
279 |
280 | inline var View.top_toBottomViewOf: View?
281 | get() {
282 | return null
283 | }
284 | set(value) {
285 | updateLayoutParams {
286 | topToBottom = value?.id ?: -1
287 | topToTop = -1
288 | }
289 | }
290 |
291 | inline var View.top_toTopOf: String
292 | get() {
293 | return ""
294 | }
295 | set(value) {
296 | updateLayoutParams {
297 | topToTop = value.toLayoutId()
298 | topToBottom = -1
299 | }
300 | }
301 |
302 | inline var View.top_toTopViewOf: View?
303 | get() {
304 | return null
305 | }
306 | set(value) {
307 |
308 | updateLayoutParams {
309 | topToTop = value?.id ?: -1
310 | topToBottom = -1
311 | }
312 | }
313 |
314 | inline var View.end_toEndOf: String
315 | get() {
316 | return ""
317 | }
318 | set(value) {
319 | updateLayoutParams {
320 | endToEnd = value.toLayoutId()
321 | endToStart = -1
322 | }
323 | }
324 |
325 | inline var View.end_toEndViewOf: View?
326 | get() {
327 | return null
328 | }
329 | set(value) {
330 | updateLayoutParams {
331 | endToEnd = value?.id ?: -1
332 | endToStart = -1
333 | }
334 | }
335 |
336 | inline var View.end_toStartOf: String
337 | get() {
338 | return ""
339 | }
340 | set(value) {
341 | updateLayoutParams {
342 | endToStart = value.toLayoutId()
343 | endToEnd = -1
344 | }
345 | }
346 |
347 | inline var View.end_toStartViewOf: View?
348 | get() {
349 | return null
350 | }
351 | set(value) {
352 | updateLayoutParams {
353 | endToStart = value?.id ?: -1
354 | endToEnd = -1
355 | }
356 | }
357 |
358 | inline var View.bottom_toBottomOf: String
359 | get() {
360 | return ""
361 | }
362 | set(value) {
363 | updateLayoutParams {
364 | bottomToBottom = value.toLayoutId()
365 | bottomToTop = -1
366 | }
367 | }
368 |
369 | inline var View.bottom_toBottomViewOf: View?
370 | get() {
371 | return null
372 | }
373 | set(value) {
374 | updateLayoutParams {
375 | bottomToBottom = value?.id ?: -1
376 | bottomToTop = -1
377 | }
378 | }
379 |
380 | inline var View.bottom_toTopOf: String
381 | get() {
382 | return ""
383 | }
384 | set(value) {
385 | updateLayoutParams {
386 | bottomToTop = value.toLayoutId()
387 | bottomToBottom = -1
388 | }
389 | }
390 |
391 | inline var View.bottom_toTopViewOf: View?
392 | get() {
393 | return null
394 | }
395 | set(value) {
396 | updateLayoutParams {
397 | bottomToTop = value?.id ?: -1
398 | bottomToBottom = -1
399 | }
400 | }
401 |
402 | inline var View.horizontal_chain_style: Int
403 | get() {
404 | return -1
405 | }
406 | set(value) {
407 | updateLayoutParams {
408 | horizontalChainStyle = value
409 | }
410 | }
411 |
412 | inline var View.vertical_chain_style: Int
413 | get() {
414 | return -1
415 | }
416 | set(value) {
417 | updateLayoutParams {
418 | verticalChainStyle = value
419 | }
420 | }
421 |
422 | inline var View.horizontal_bias: Float
423 | get() {
424 | return -1f
425 | }
426 | set(value) {
427 | updateLayoutParams {
428 | horizontalBias = value
429 | }
430 | }
431 |
432 | inline var View.dimension_radio: String
433 | get() {
434 | return ""
435 | }
436 | set(value) {
437 | updateLayoutParams {
438 | dimensionRatio = value
439 | }
440 | }
441 |
442 | inline var View.vertical_bias: Float
443 | get() {
444 | return -1f
445 | }
446 | set(value) {
447 | updateLayoutParams {
448 | verticalBias = value
449 | }
450 | }
451 |
452 | inline var View.center_horizontal: Boolean
453 | get() {
454 | return false
455 | }
456 | set(value) {
457 | if (!value) return
458 | start_toStartOf = parent_id
459 | end_toEndOf = parent_id
460 | }
461 |
462 | inline var View.center_vertical: Boolean
463 | get() {
464 | return false
465 | }
466 | set(value) {
467 | if (!value) return
468 | top_toTopOf = parent_id
469 | bottom_toBottomOf = parent_id
470 | }
471 |
472 | inline var View.align_vertical_to: String
473 | get() {
474 | return ""
475 | }
476 | set(value) {
477 | top_toTopOf = value
478 | bottom_toBottomOf = value
479 | }
480 |
481 | inline var View.align_horizontal_to: String
482 | get() {
483 | return ""
484 | }
485 | set(value) {
486 | start_toStartOf = value
487 | end_toEndOf = value
488 | }
489 |
490 | inline var View.width_percentage: Float
491 | get() {
492 | return -1f
493 | }
494 | set(value) {
495 | updateLayoutParams {
496 | width = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
497 | matchConstraintPercentWidth = value
498 | }
499 | }
500 |
501 | inline var View.height_percentage: Float
502 | get() {
503 | return -1f
504 | }
505 | set(value) {
506 | updateLayoutParams {
507 | height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
508 | matchConstraintPercentHeight = value
509 | }
510 | }
511 |
512 | inline var View.background_color: String
513 | get() {
514 | return ""
515 | }
516 | set(value) {
517 | setBackgroundColor(Color.parseColor(value))
518 | }
519 |
520 | inline var View.background_color_res: Int
521 | get() {
522 | return -1
523 | }
524 | set(value) {
525 | setBackgroundColor(ContextCompat.getColor(context, value))
526 | }
527 |
528 | inline var View.background_res: Int
529 | get() {
530 | return -1
531 | }
532 | set(value) {
533 | background = AppCompatResources.getDrawable(context, value)
534 | }
535 |
536 | inline var View.background_vector: Int
537 | get() {
538 | return -1
539 | }
540 | set(value) {
541 | val drawable = VectorDrawableCompat.create(context.getResources(), value, null)
542 | background = drawable
543 | }
544 |
545 | inline var View.background_drawable: Drawable?
546 | get() {
547 | return null
548 | }
549 | set(value) {
550 | value?.let { background = it }
551 | }
552 |
553 | inline var View.background_drawable_state_list: List>
554 | get() {
555 | return listOf(intArrayOf() to GradientDrawable())
556 | }
557 | set(value) {
558 | background = StateListDrawable().apply {
559 | value.forEach { pair ->
560 | addState(pair.first, pair.second)
561 | }
562 | }
563 | }
564 |
565 | inline var View.margin_top: Number
566 | get() {
567 | return -1
568 | }
569 | set(value) {
570 | updateLayoutParams {
571 | topMargin = value.dp
572 | }
573 | }
574 |
575 | inline var View.margin_bottom: Number
576 | get() {
577 | return -1
578 | }
579 | set(value) {
580 | updateLayoutParams {
581 | bottomMargin = value.dp
582 | }
583 | }
584 |
585 | inline var View.margin_start: Number
586 | get() {
587 | return -1
588 | }
589 | set(value) {
590 | updateLayoutParams {
591 | MarginLayoutParamsCompat.setMarginStart(this, value.dp)
592 | }
593 | }
594 |
595 | inline var View.margin_end: Number
596 | get() {
597 | return -1
598 | }
599 | set(value) {
600 | updateLayoutParams {
601 | MarginLayoutParamsCompat.setMarginEnd(this, value.dp)
602 | }
603 | }
604 |
605 | inline var View.margin_horizontal: Number
606 | get() {
607 | return -1
608 | }
609 | set(value) {
610 | updateLayoutParams {
611 | MarginLayoutParamsCompat.setMarginEnd(this, value.dp)
612 | MarginLayoutParamsCompat.setMarginStart(this, value.dp)
613 | }
614 | }
615 |
616 | inline var View.margin_vertical: Number
617 | get() {
618 | return -1
619 | }
620 | set(value) {
621 | updateLayoutParams {
622 | topMargin = value.dp
623 | bottomMargin = value.dp
624 | }
625 | }
626 |
627 | inline var View.gone_margin_end: Number
628 | get() {
629 | return -1
630 | }
631 | set(value) {
632 | updateLayoutParams {
633 | goneEndMargin = value.dp
634 | }
635 | }
636 |
637 | inline var View.gone_margin_start: Number
638 | get() {
639 | return -1
640 | }
641 | set(value) {
642 | updateLayoutParams {
643 | goneStartMargin = value.dp
644 | }
645 | }
646 |
647 | inline var View.gone_margin_top: Number
648 | get() {
649 | return -1
650 | }
651 | set(value) {
652 | updateLayoutParams {
653 | goneTopMargin = value.dp
654 | }
655 | }
656 |
657 | inline var View.gone_margin_bottom: Number
658 | get() {
659 | return -1
660 | }
661 | set(value) {
662 | updateLayoutParams {
663 | goneBottomMargin = value.dp
664 | }
665 | }
666 |
667 | inline var View.guide_percentage: Float
668 | get() {
669 | return -1f
670 | }
671 | set(value) {
672 | updateLayoutParams {
673 | guidePercent = value
674 | }
675 | }
676 |
677 | inline var View.guide_orientation: Int
678 | get() {
679 | return 1
680 | }
681 | set(value) {
682 | updateLayoutParams {
683 | orientation = value
684 | }
685 | }
686 |
687 | inline var View.layout_visibility: Int
688 | get() {
689 | return -1
690 | }
691 | set(value) {
692 |
693 | visibility = value
694 | }
695 |
696 | /**
697 | * bind async data
698 | */
699 | inline var View.bindLiveData: LiveDataBinder?
700 | get() {
701 | return null
702 | }
703 | set(value) {
704 | observe(value?.liveData) {
705 | value?.action?.invoke(it)
706 | }
707 | }
708 |
709 | /**
710 | * old fashion for binding data
711 | */
712 | inline var View.bind: Binder?
713 | get() {
714 | return null
715 | }
716 | set(value) {
717 | value?.action?.invoke(this, value.data)
718 | }
719 |
720 | /**
721 | * bind sync data
722 | */
723 | inline var View.bindData: () -> Unit
724 | get() {
725 | return {}
726 | }
727 | set(value) {
728 | value()
729 | }
730 |
731 | inline var View.fitsSystemWindows: Boolean
732 | get() {
733 | return false
734 | }
735 | set(value) {
736 | fitsSystemWindows = value
737 | }
738 |
739 | /**
740 | * use this attribute to build shape dynamically, getting rid of "shape.xml"
741 | */
742 | inline var View.shape: GradientDrawable
743 | get() {
744 | return GradientDrawable()
745 | }
746 | set(value) {
747 | background = value
748 | }
749 |
750 | inline var ImageView.src: Int
751 | get() {
752 | return -1
753 | }
754 | set(value) {
755 | setImageDrawable(AppCompatResources.getDrawable(context, value))
756 | }
757 |
758 | inline var ImageView.imageDrawable: Drawable?
759 | get() {
760 | return null
761 | }
762 | set(value) {
763 | setImageDrawable(value)
764 | }
765 |
766 | inline var ImageView.bitmap: Bitmap?
767 | get() {
768 | return null
769 | }
770 | set(value) {
771 | setImageBitmap(value)
772 | }
773 |
774 | inline var TextView.maxLength: Int
775 | get() {
776 | return 1
777 | }
778 | set(value) {
779 | filters = arrayOf(LengthFilter(value))
780 | }
781 |
782 | inline var TextView.text_res: Int
783 | get() {
784 | return -1
785 | }
786 | set(value) {
787 | setText(value)
788 | }
789 |
790 | inline var TextView.hint_color: String
791 | get() {
792 | return ""
793 | }
794 | set(value) {
795 | setHintTextColor(Color.parseColor(value))
796 | }
797 |
798 | inline var TextView.hint_color_res: Int
799 | get() {
800 | return -1
801 | }
802 | set(value) {
803 | setHintTextColor(ContextCompat.getColor(context, value))
804 | }
805 |
806 | inline var TextView.hint_text_res: Int
807 | get() {
808 | return -1
809 | }
810 | set(value) {
811 | setHint(value)
812 | }
813 |
814 | inline var TextView.hint_text: String
815 | get() {
816 | return ""
817 | }
818 | set(value) {
819 | setHint(value)
820 | }
821 |
822 | inline var TextView.line_space_multiplier: Float
823 | get() {
824 | return -1f
825 | }
826 | set(value) {
827 | setLineSpacing(lineSpacingExtra, value)
828 | }
829 |
830 | inline var TextView.line_space_extra: Float
831 | get() {
832 | return -1f
833 | }
834 | set(value) {
835 | setLineSpacing(value, lineSpacingMultiplier)
836 | }
837 |
838 | inline var TextView.textStyle: Int
839 | get() {
840 | return -1
841 | }
842 | set(value) = setTypeface(typeface, value)
843 |
844 | inline var TextView.textColor: String
845 | get() {
846 | return ""
847 | }
848 | set(value) {
849 | setTextColor(Color.parseColor(value))
850 | }
851 |
852 | inline var TextView.text_color_res: Int
853 | get() {
854 | return -1
855 | }
856 | set(value) {
857 | setTextColor(ContextCompat.getColor(context, value))
858 | }
859 |
860 | inline var TextView.fontFamily: Int
861 | get() {
862 | return 1
863 | }
864 | set(value) {
865 | try {
866 | typeface = ResourcesCompat.getFont(context, value)
867 | } catch (e: Resources.NotFoundException) {
868 | }
869 | }
870 |
871 | inline var TextView.drawable_start: Int
872 | get() {
873 | return -1
874 | }
875 | set(value) {
876 | setCompoundDrawablesRelativeWithIntrinsicBounds(value, 0, 0, 0)
877 | }
878 |
879 | inline var TextView.drawable_end: Int
880 | get() {
881 | return -1
882 | }
883 | set(value) {
884 | setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, value, 0)
885 | }
886 |
887 | inline var TextView.drawable_top: Int
888 | get() {
889 | return -1
890 | }
891 | set(value) {
892 | setCompoundDrawablesRelativeWithIntrinsicBounds(0, value, 0, 0)
893 | }
894 |
895 | inline var TextView.drawable_bottom: Int
896 | get() {
897 | return -1
898 | }
899 | set(value) {
900 | setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, value)
901 | }
902 |
903 | inline var TextView.drawable_padding: Int
904 | get() {
905 | return 0
906 | }
907 | set(value) {
908 | compoundDrawablePadding = value.dp
909 | }
910 |
911 | inline var TextView.onTextChange: TextWatcher
912 | get() {
913 | return TextWatcher()
914 | }
915 | set(value) {
916 | val textWatcher = object : android.text.TextWatcher {
917 | override fun afterTextChanged(s: Editable?) {
918 | value.afterTextChanged.invoke(s)
919 | }
920 |
921 | override fun beforeTextChanged(
922 | text: CharSequence?,
923 | start: Int,
924 | count: Int,
925 | after: Int
926 | ) {
927 | value.beforeTextChanged.invoke(text, start, count, after)
928 | }
929 |
930 | override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) {
931 | value.onTextChanged.invoke(text, start, before, count)
932 | }
933 | }
934 | addTextChangedListener(textWatcher)
935 | }
936 |
937 | inline var TextView.onEditorAction: EditorActionListener
938 | get() {
939 | return EditorActionListener()
940 | }
941 | set(value) {
942 | val editorActionListener = object : TextView.OnEditorActionListener {
943 | override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
944 | return value.onEditorAction(v, actionId, event)
945 | }
946 | }
947 | setOnEditorActionListener(editorActionListener)
948 | }
949 |
950 | inline var Button.textAllCaps: Boolean
951 | get() {
952 | return false
953 | }
954 | set(value) {
955 | isAllCaps = value
956 | }
957 |
958 |
959 | inline var NestedScrollView.fadeScrollBar: Boolean
960 | get() {
961 | return false
962 | }
963 | set(value) {
964 | isScrollbarFadingEnabled = true
965 | }
966 |
967 | inline var ConstraintHelper.referenceIds: String
968 | get() {
969 | return ""
970 | }
971 | set(value) {
972 | referencedIds = value.split(",").map { it.toLayoutId() }.toIntArray()
973 | }
974 |
975 | inline var Flow.flow_horizontalGap: Int
976 | get() {
977 | return 0
978 | }
979 | set(value) {
980 | setHorizontalGap(value.dp)
981 | }
982 |
983 | inline var Flow.flow_verticalGap: Int
984 | get() {
985 | return 0
986 | }
987 | set(value) {
988 | setVerticalGap(value.dp)
989 | }
990 |
991 | inline var Flow.flow_wrapMode: Int
992 | get() {
993 | return 0
994 | }
995 | set(value) {
996 | setWrapMode(value)
997 | }
998 |
999 | inline var Flow.flow_padding: Int
1000 | get() {
1001 | return 0
1002 | }
1003 | set(value) {
1004 | setPadding(value.dp)
1005 | }
1006 | inline var Flow.flow_paddingRight: Int
1007 | get() {
1008 | return 0
1009 | }
1010 | set(value) {
1011 | setPaddingRight(value.dp)
1012 | }
1013 |
1014 | inline var Flow.flow_paddingTop: Int
1015 | get() {
1016 | return 0
1017 | }
1018 | set(value) {
1019 | setPaddingTop(value.dp)
1020 | }
1021 | inline var Flow.flow_paddingBottom: Int
1022 | get() {
1023 | return 0
1024 | }
1025 | set(value) {
1026 | setPaddingBottom(value.dp)
1027 | }
1028 | inline var Flow.flow_paddingLeft: Int
1029 | get() {
1030 | return 0
1031 | }
1032 | set(value) {
1033 | setPaddingLeft(value.dp)
1034 | }
1035 | inline var Flow.flow_orientation: Int
1036 | get() {
1037 | return 0
1038 | }
1039 | set(value) {
1040 | setOrientation(value)
1041 | }
1042 | inline var Flow.flow_horizontalStyle: Int
1043 | get() {
1044 | return 0
1045 | }
1046 | set(value) {
1047 | setHorizontalStyle(value)
1048 | }
1049 | inline var Flow.flow_verticalStyle: Int
1050 | get() {
1051 | return 0
1052 | }
1053 | set(value) {
1054 | setVerticalStyle(value)
1055 | }
1056 | inline var Flow.flow_horizontalBias: Float
1057 | get() {
1058 | return 0f
1059 | }
1060 | set(value) {
1061 | setHorizontalBias(value)
1062 | }
1063 | inline var Flow.flow_verticalBias: Float
1064 | get() {
1065 | return 0f
1066 | }
1067 | set(value) {
1068 | setVerticalBias(value)
1069 | }
1070 | inline var Flow.flow_horizontalAlign: Int
1071 | get() {
1072 | return 0
1073 | }
1074 | set(value) {
1075 | setHorizontalAlign(value)
1076 | }
1077 | inline var Flow.flow_verticalAlign: Int
1078 | get() {
1079 | return 0
1080 | }
1081 | set(value) {
1082 | setVerticalAlign(value)
1083 | }
1084 | inline var Flow.flow_maxElementWrap: Int
1085 | get() {
1086 | return 0
1087 | }
1088 | set(value) {
1089 | setMaxElementsWrap(value)
1090 | }
1091 |
1092 |
1093 | inline var ConstraintHelper.reference_ids: List
1094 | get() {
1095 | return emptyList()
1096 | }
1097 | set(value) {
1098 | referencedIds = value.map { it.toLayoutId() }.toIntArray()
1099 | }
1100 |
1101 | inline var Barrier.barrier_direction: Int
1102 | get() {
1103 | return -1
1104 | }
1105 | set(value) {
1106 | type = value
1107 | }
1108 |
1109 | var View.onClick: (View) -> Unit
1110 | get() {
1111 | return {}
1112 | }
1113 | set(value) {
1114 | setOnClickListener { v -> value(v) }
1115 | }
1116 |
1117 | var View.shakelessClick: (View) -> Unit
1118 | get() {
1119 | return {}
1120 | }
1121 | set(value) {
1122 | setShakelessClickListener(1000) {
1123 | value(it)
1124 | }
1125 | }
1126 |
1127 | var RecyclerView.onItemClick: (View, Int, Float, Float) -> Boolean
1128 | get() {
1129 | return { _, _, _, _ -> false }
1130 | }
1131 | set(value) {
1132 | setOnItemClickListener(value)
1133 | }
1134 |
1135 | var RecyclerView.onItemLongClick: (View, Int, Float, Float) -> Unit
1136 | get() {
1137 | return { _, _, _, _ -> }
1138 | }
1139 | set(value) {
1140 | setOnItemLongClickListener(value)
1141 | }
1142 |
1143 | var RecyclerView.hasFixedSize: Boolean
1144 | get() {
1145 | return false
1146 | }
1147 | set(value) {
1148 | setHasFixedSize(value)
1149 | }
1150 | //
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/dsl/LayoutBuilder.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.dsl
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.*
7 | import androidx.appcompat.view.ContextThemeWrapper
8 | import androidx.appcompat.widget.*
9 | import androidx.constraintlayout.helper.widget.Flow
10 | import androidx.constraintlayout.helper.widget.Layer
11 | import androidx.constraintlayout.widget.Barrier
12 | import androidx.constraintlayout.widget.ConstraintLayout
13 | import androidx.constraintlayout.widget.Guideline
14 | import androidx.core.widget.NestedScrollView
15 | import androidx.fragment.app.Fragment
16 | import androidx.fragment.app.FragmentContainerView
17 | import androidx.recyclerview.widget.RecyclerView
18 | import androidx.viewpager2.widget.ViewPager2
19 |
20 | /**
21 | * the extension functions and field in this file help you to build layout dynamically,
22 | * which has a better performance than xml files and more readable than normal java and kotlin code
23 | *
24 | * using this dsl to build view in kotlin like the following:
25 | * private val rootView by lazy {
26 | * ConstraintLayout {
27 | * layout_width = match_parent
28 | * layout_height = match_parent
29 | *
30 | * ImageView {
31 | * layout_id = "ivBack"
32 | * layout_width = 40
33 | * layout_height = 40
34 | * margin_start = 20
35 | * margin_top = 20
36 | * src = R.drawable.ic_back_black
37 | * start_toStartOf = parent_id
38 | * top_toTopOf = parent_id
39 | * onClick = onBackClick
40 | * }
41 | *
42 | * TextView {
43 | * layout_width = wrap_content
44 | * layout_height = wrap_content
45 | * text = "commit"
46 | * textSize = 30f
47 | * layout_visibility = gone
48 | * textStyle = bold
49 | * align_vertical_to = "ivBack"
50 | * center_horizontal = true
51 | * }
52 | * }
53 | * }
54 | */
55 |
56 |
57 | /**
58 | * create [AppCompatTextView] instance within a [ViewGroup]
59 | * @param style an style int value defined in xml
60 | * @param autoAdd whether add [AppCompatTextView] into [ViewGroup] automatically
61 | * @param init set attributes for this view in this lambda
62 | */
63 | inline fun ViewGroup.TextView(
64 | style: Int? = null,
65 | autoAdd: Boolean = true,
66 | init: AppCompatTextView.() -> Unit
67 | ): TextView {
68 | val textView =
69 | if (style != null) AppCompatTextView(ContextThemeWrapper(context, style))
70 | else AppCompatTextView(context)
71 | return textView.apply(init).also { if (autoAdd) addView(it) }
72 | }
73 |
74 | /**
75 | * create [AppCompatImageView] instance within a [ViewGroup]
76 | * @param style an style int value defined in xml
77 | * @param autoAdd whether add [AppCompatImageView] into [ViewGroup] automatically
78 | * @param init set attributes for this view in this lambda
79 | */
80 | inline fun ViewGroup.ImageView(
81 | style: Int? = null,
82 | autoAdd: Boolean = true,
83 | init: AppCompatImageView.() -> Unit
84 | ): ImageView {
85 | val imageView =
86 | if (style != null) AppCompatImageView(
87 | ContextThemeWrapper(context, style)
88 | ) else AppCompatImageView(context)
89 | return imageView.apply(init).also { if (autoAdd) addView(it) }
90 | }
91 |
92 | /**
93 | * create [AppCompatButton] instance within a [ViewGroup]
94 | * @param style an style int value defined in xml
95 | * @param autoAdd whether add [AppCompatButton] into [ViewGroup] automatically
96 | * @param init set attributes for this view in this lambda
97 | */
98 | inline fun ViewGroup.Button(
99 | style: Int? = null,
100 | autoAdd: Boolean = true,
101 | init: AppCompatButton.() -> Unit
102 | ): Button {
103 | val button =
104 | if (style != null) AppCompatButton(
105 | ContextThemeWrapper(context, style)
106 | ) else AppCompatButton(context)
107 | return button.apply(init).also { if (autoAdd) addView(it) }
108 | }
109 |
110 | /**
111 | * create [View] instance within a [ViewGroup]
112 | * @param style an style int value defined in xml
113 | * @param autoAdd whether add [View] into [ViewGroup] automatically
114 | * @param init set attributes for this view in this lambda
115 | */
116 | inline fun ViewGroup.View(
117 | style: Int? = null,
118 | autoAdd: Boolean = true,
119 | init: View.() -> Unit
120 | ): View {
121 | val view =
122 | if (style != null) View(
123 | ContextThemeWrapper(context, style)
124 | ) else View(context)
125 | return view.apply(init).also { if (autoAdd) addView(it) }
126 | }
127 |
128 | /**
129 | * create [RelativeLayout] instance within a [ViewGroup]
130 | * @param style an style int value defined in xml
131 | * @param autoAdd whether add [RelativeLayout] into [ViewGroup] automatically
132 | * @param init set attributes for this view in this lambda
133 | */
134 | inline fun ViewGroup.RelativeLayout(
135 | style: Int? = null,
136 | autoAdd: Boolean = true,
137 | init: RelativeLayout.() -> Unit
138 | ): RelativeLayout {
139 | val relativeLayout =
140 | if (style != null) RelativeLayout(
141 | ContextThemeWrapper(context, style)
142 | ) else RelativeLayout(context)
143 | return relativeLayout.apply(init).also { if (autoAdd) addView(it) }
144 | }
145 |
146 | /**
147 | * create [LinearLayout] instance within a [ViewGroup]
148 | * @param style an style int value defined in xml
149 | * @param autoAdd whether add [LinearLayoutCompat] into [ViewGroup] automatically
150 | * @param init set attributes for this view in this lambda
151 | */
152 | inline fun ViewGroup.LinearLayout(
153 | style: Int? = null,
154 | autoAdd: Boolean = true,
155 | init: LinearLayout.() -> Unit
156 | ): LinearLayout {
157 | val linearLayout =
158 | if (style != null) LinearLayout(
159 | ContextThemeWrapper(context, style)
160 | ) else LinearLayout(context)
161 | return linearLayout.apply(init).also { if (autoAdd) addView(it) }
162 | }
163 |
164 | /**
165 | * create [NestedScrollView] instance within a [ViewGroup]
166 | * @param style an style int value defined in xml
167 | * @param autoAdd whether add [NestedScrollView] into [ViewGroup] automatically
168 | * @param init set attributes for this view in this lambda
169 | */
170 | inline fun ViewGroup.NestedScrollView(
171 | style: Int? = null,
172 | autoAdd: Boolean = true,
173 | init: NestedScrollView.() -> Unit
174 | ): NestedScrollView {
175 | val nestedScrollView =
176 | if (style != null) NestedScrollView(
177 | ContextThemeWrapper(context, style)
178 | ) else NestedScrollView(context)
179 | return nestedScrollView.apply(init).also { if (autoAdd) addView(it) }
180 | }
181 |
182 | /**
183 | * create [RecyclerView] instance within a [ViewGroup]
184 | * @param style an style int value defined in xml
185 | * @param autoAdd whether add [RecyclerView] into [ViewGroup] automatically
186 | * @param init set attributes for this view in this lambda
187 | */
188 | inline fun ViewGroup.RecyclerView(
189 | style: Int? = null,
190 | autoAdd: Boolean = true,
191 | init: RecyclerView.() -> Unit
192 | ): RecyclerView {
193 | val recyclerView =
194 | if (style != null) RecyclerView(
195 | ContextThemeWrapper(context, style)
196 | ) else RecyclerView(context)
197 | return recyclerView.apply(init).also { if (autoAdd) addView(it) }
198 | }
199 |
200 | /**
201 | * create [ConstraintLayout] instance within a [ViewGroup]
202 | * @param style an style int value defined in xml
203 | * @param autoAdd whether add [ConstraintLayout] into [ViewGroup] automatically
204 | * @param init set attributes for this view in this lambda
205 | */
206 | inline fun ViewGroup.ConstraintLayout(
207 | style: Int? = null,
208 | autoAdd: Boolean = true,
209 | init: ConstraintLayout.() -> Unit
210 | ): ConstraintLayout {
211 | val constraintLayout =
212 | if (style != null) ConstraintLayout(
213 | ContextThemeWrapper(context, style)
214 | ) else ConstraintLayout(context)
215 | return constraintLayout.apply(init).also { if (autoAdd) addView(it) }
216 | }
217 |
218 | /**
219 | * create [FrameLayout] instance within a [ViewGroup]
220 | * @param style an style int value defined in xml
221 | * @param autoAdd whether add [FrameLayout] into [ViewGroup] automatically
222 | * @param init set attributes for this view in this lambda
223 | */
224 | inline fun ViewGroup.FrameLayout(
225 | style: Int? = null,
226 | autoAdd: Boolean = true,
227 | init: FrameLayout.() -> Unit
228 | ): FrameLayout {
229 | val frameLayout =
230 | if (style != null) FrameLayout(
231 | ContextThemeWrapper(context, style)
232 | ) else FrameLayout(context)
233 | return frameLayout.apply(init).also { if (autoAdd) addView(it) }
234 | }
235 |
236 | /**
237 | * create [ViewFlipper] instance within a [ViewGroup]
238 | * @param style an style int value defined in xml
239 | * @param autoAdd whether add [ViewFlipper] into [ViewGroup] automatically
240 | * @param init set attributes for this view in this lambda
241 | */
242 | inline fun ViewGroup.ViewFlipper(
243 | style: Int? = null,
244 | autoAdd: Boolean = true,
245 | init: ViewFlipper.() -> Unit
246 | ): ViewFlipper {
247 | val viewFlipper =
248 | if (style != null) ViewFlipper(
249 | ContextThemeWrapper(context, style)
250 | ) else ViewFlipper(context)
251 | return viewFlipper.apply(init).also { if (autoAdd) addView(it) }
252 | }
253 |
254 | /**
255 | * create [AppCompatEditText] instance within a [ViewGroup]
256 | * @param style an style int value defined in xml
257 | * @param autoAdd whether add [AppCompatEditText] into [ViewGroup] automatically
258 | * @param init set attributes for this view in this lambda
259 | */
260 | inline fun ViewGroup.EditText(
261 | style: Int? = null,
262 | autoAdd: Boolean = true,
263 | init: AppCompatEditText.() -> Unit
264 | ): AppCompatEditText {
265 | val editText =
266 | if (style != null) AppCompatEditText(
267 | ContextThemeWrapper(context, style)
268 | ) else AppCompatEditText(context)
269 | return editText.apply(init).also { if (autoAdd) addView(it) }
270 | }
271 |
272 | /**
273 | * create [HorizontalScrollView] instance within a [ViewGroup]
274 | * @param style an style int value defined in xml
275 | * @param autoAdd whether add [HorizontalScrollView] into [ViewGroup] automatically
276 | * @param init set attributes for this view in this lambda
277 | */
278 | inline fun ViewGroup.HorizontalScrollView(
279 | style: Int? = null,
280 | autoAdd: Boolean = true,
281 | init: HorizontalScrollView.() -> Unit
282 | ): HorizontalScrollView {
283 | val horizontalScrollView =
284 | if (style != null) HorizontalScrollView(
285 | ContextThemeWrapper(context, style)
286 | ) else HorizontalScrollView(context)
287 | return horizontalScrollView.apply(init).also { if (autoAdd) addView(it) }
288 | }
289 |
290 | /**
291 | * create [ViewPager2] instance within a [ViewGroup]
292 | * @param style an style int value defined in xml
293 | * @param autoAdd whether add [ViewPager2] into [ViewGroup] automatically
294 | * @param init set attributes for this view in this lambda
295 | */
296 | inline fun ViewGroup.ViewPager2(
297 | style: Int? = null,
298 | autoAdd: Boolean = true,
299 | init: ViewPager2.() -> Unit
300 | ): ViewPager2 {
301 | val viewPager2 =
302 | if (style != null) ViewPager2(
303 | ContextThemeWrapper(context, style)
304 | ) else ViewPager2(context)
305 | return viewPager2.apply(init).also { if (autoAdd) addView(it) }
306 | }
307 | /**
308 | * create [FragmentContainerView] instance within a [ViewGroup]
309 | * @param style an style int value defined in xml
310 | * @param autoAdd whether add [FragmentContainerView] into [ViewGroup] automatically
311 | * @param init set attributes for this view in this lambda
312 | */
313 | inline fun ViewGroup.FragmentContainerView(
314 | style: Int? = null,
315 | autoAdd: Boolean = true,
316 | init: FragmentContainerView.() -> Unit
317 | ): FragmentContainerView {
318 | val fragmentContainerView =
319 | if (style != null) FragmentContainerView(
320 | ContextThemeWrapper(context, style)
321 | ) else FragmentContainerView(context)
322 | return fragmentContainerView.apply(init).also { if (autoAdd) addView(it) }
323 | }
324 |
325 | /**
326 | * create [Guideline] instance within a [ConstraintLayout]
327 | * @param style an style int value defined in xml
328 | * @param autoAdd whether add [Guideline] into [ConstraintLayout] automatically
329 | * @param init set attributes for this view in this lambda
330 | */
331 | inline fun ConstraintLayout.Guideline(
332 | style: Int? = null,
333 | autoAdd: Boolean = true,
334 | init: Guideline.() -> Unit
335 | ): Guideline {
336 | val guideline =
337 | if (style != null) Guideline(
338 | ContextThemeWrapper(context, style)
339 | ) else Guideline(context)
340 | return guideline.apply(init).also { if (autoAdd) addView(it) }
341 | }
342 |
343 | /**
344 | * create [Barrier] instance within a [ConstraintLayout]
345 | * @param style an style int value defined in xml
346 | * @param autoAdd whether add [Barrier] into [ConstraintLayout] automatically
347 | * @param init set attributes for this view in this lambda
348 | */
349 | inline fun ConstraintLayout.Barrier(
350 | style: Int? = null,
351 | autoAdd: Boolean = true,
352 | init: Barrier.() -> Unit
353 | ): Barrier {
354 | val barrier =
355 | if (style != null) Barrier(
356 | ContextThemeWrapper(context, style)
357 | ) else Barrier(context)
358 | return barrier.apply(init).also { if (autoAdd) addView(it) }
359 | }
360 |
361 | /**
362 | * create [Flow] instance within a [ConstraintLayout]
363 | * @param style an style int value defined in xml
364 | * @param autoAdd whether add [Flow] into [ConstraintLayout] automatically
365 | * @param init set attributes for this view in this lambda
366 | */
367 | inline fun ConstraintLayout.Flow(
368 | style: Int? = null,
369 | autoAdd: Boolean = true,
370 | init: Flow.() -> Unit
371 | ): Flow {
372 | val flow =
373 | if (style != null) Flow(
374 | ContextThemeWrapper(context, style)
375 | ) else Flow(context)
376 | return flow.apply(init).also { if (autoAdd) addView(it) }
377 | }
378 |
379 | /**
380 | * create [Layer] instance within a [ConstraintLayout]
381 | * @param style an style int value defined in xml
382 | * @param autoAdd whether add [Layer] into [ConstraintLayout] automatically
383 | * @param init set attributes for this view in this lambda
384 | */
385 | inline fun ConstraintLayout.Layer(
386 | style: Int? = null,
387 | autoAdd: Boolean = true,
388 | init: Layer.() -> Unit
389 | ): Layer {
390 | val layer =
391 | if (style != null) Layer(
392 | ContextThemeWrapper(context, style)
393 | ) else Layer(context)
394 | return layer.apply(init).also { if (autoAdd) addView(it) }
395 | }
396 |
397 | /**
398 | * create [ConstraintLayout] instance
399 | * @param style an style int value defined in xml
400 | * @param init set attributes for this view in this lambda
401 | */
402 | inline fun Context.ConstraintLayout(
403 | style: Int? = null,
404 | init: ConstraintLayout.() -> Unit
405 | ): ConstraintLayout {
406 | val constraintLayout =
407 | if (style != null) ConstraintLayout(
408 | ContextThemeWrapper(this, style)
409 | ) else ConstraintLayout(this)
410 | return constraintLayout.apply(init)
411 | }
412 |
413 | /**
414 | * create [LinearLayout] instance
415 | * @param style an style int value defined in xml
416 | * @param init set attributes for this view in this lambda
417 | */
418 | inline fun Context.LinearLayout(
419 | style: Int? = null,
420 | init: LinearLayout.() -> Unit
421 | ): LinearLayout {
422 | val LinearLayout =
423 | if (style != null) LinearLayout(
424 | ContextThemeWrapper(this, style)
425 | ) else LinearLayout(this)
426 | return LinearLayout.apply(init)
427 | }
428 |
429 | /**
430 | * create [FrameLayout] instance
431 | * @param style an style int value defined in xml
432 | * @param init set attributes for this view in this lambda
433 | */
434 | inline fun Context.FrameLayout(style: Int? = null, init: FrameLayout.() -> Unit): FrameLayout {
435 | val frameLayout =
436 | if (style != null) FrameLayout(
437 | ContextThemeWrapper(this, style)
438 | ) else FrameLayout(this)
439 | return frameLayout.apply(init)
440 | }
441 |
442 | /**
443 | * create [NestedScrollView] instance
444 | * @param style an style int value defined in xml
445 | * @param init set attributes for this view in this lambda
446 | */
447 | inline fun Context.NestedScrollView(
448 | style: Int? = null,
449 | init: NestedScrollView.() -> Unit
450 | ): NestedScrollView {
451 | val nestedScrollView =
452 | if (style != null) NestedScrollView(
453 | ContextThemeWrapper(this, style)
454 | ) else NestedScrollView(this)
455 | return nestedScrollView.apply(init)
456 | }
457 |
458 | /**
459 | * create [AppCompatTextView] instance
460 | * @param style an style int value defined in xml
461 | * @param init set attributes for this view in this lambda
462 | */
463 | inline fun Context.TextView(
464 | style: Int? = null,
465 | init: AppCompatTextView.() -> Unit
466 | ): AppCompatTextView {
467 | val textView =
468 | if (style != null) AppCompatTextView(
469 | ContextThemeWrapper(this, style)
470 | ) else AppCompatTextView(this)
471 | return textView.apply(init)
472 |
473 | }
474 |
475 | /**
476 | * create [AppCompatButton] instance
477 | * @param style an style int value defined in xml
478 | * @param init set attributes for this view in this lambda
479 | */
480 | inline fun Context.Button(style: Int? = null, init: AppCompatButton.() -> Unit): AppCompatButton {
481 | val button =
482 | if (style != null) AppCompatButton(
483 | ContextThemeWrapper(this, style)
484 | ) else AppCompatButton(this)
485 | return button.apply(init)
486 | }
487 |
488 | /**
489 | * create [AppCompatImageView] instance
490 | * @param style an style int value defined in xml
491 | * @param init set attributes for this view in this lambda
492 | */
493 | inline fun Context.ImageView(
494 | style: Int? = null,
495 | init: AppCompatImageView.() -> Unit
496 | ): AppCompatImageView {
497 | val imageView =
498 | if (style != null) AppCompatImageView(
499 | ContextThemeWrapper(this, style)
500 | ) else AppCompatImageView(this)
501 | return imageView.apply(init)
502 | }
503 |
504 | /**
505 | * create [View] instance
506 | * @param style an style int value defined in xml
507 | * @param init set attributes for this view in this lambda
508 | */
509 | inline fun Context.View(style: Int? = null, init: View.() -> Unit): View {
510 | val view =
511 | if (style != null) View(
512 | ContextThemeWrapper(this, style)
513 | ) else View(this)
514 | return view.apply(init)
515 | }
516 |
517 | /**
518 | * create [AppCompatEditText] instance
519 | * @param style an style int value defined in xml
520 | * @param init set attributes for this view in this lambda
521 | */
522 | inline fun Context.EditText(
523 | style: Int? = null,
524 | init: AppCompatEditText.() -> Unit
525 | ): AppCompatEditText {
526 | val editText =
527 | if (style != null) AppCompatEditText(
528 | ContextThemeWrapper(this, style)
529 | ) else AppCompatEditText(this)
530 | return editText.apply(init)
531 | }
532 |
533 | /**
534 | * create [ViewFlipper] instance
535 | * @param style an style int value defined in xml
536 | * @param init set attributes for this view in this lambda
537 | */
538 | inline fun Context.ViewFlipper(style: Int? = null, init: ViewFlipper.() -> Unit): ViewFlipper {
539 | val viewFlipper =
540 | if (style != null) ViewFlipper(
541 | ContextThemeWrapper(this, style)
542 | ) else ViewFlipper(this)
543 | return viewFlipper.apply(init)
544 |
545 | }
546 |
547 | /**
548 | * create [HorizontalScrollView] instance
549 | * @param style an style int value defined in xml
550 | * @param init set attributes for this view in this lambda
551 | */
552 | inline fun Context.HorizontalScrollView(
553 | style: Int? = null,
554 | init: HorizontalScrollView.() -> Unit
555 | ): HorizontalScrollView {
556 | val horizontalScrollView =
557 | if (style != null) HorizontalScrollView(
558 | ContextThemeWrapper(this, style)
559 | ) else HorizontalScrollView(this)
560 | return horizontalScrollView.apply(init)
561 | }
562 |
563 | /**
564 | * create [ViewPager2] instance
565 | * @param style an style int value defined in xml
566 | * @param init set attributes for this view in this lambda
567 | */
568 | inline fun Context.ViewPager2(
569 | style: Int? = null,
570 | init: ViewPager2.() -> Unit
571 | ): ViewPager2 {
572 | val viewPager2 =
573 | if (style != null) ViewPager2(
574 | ContextThemeWrapper(this, style)
575 | ) else ViewPager2(this)
576 | return viewPager2.apply(init)
577 | }
578 |
579 | /**
580 | * create [ViewPager2] instance
581 | * @param style an style int value defined in xml
582 | * @param init set attributes for this view in this lambda
583 | */
584 | inline fun Context.RecyclerView(
585 | style: Int? = null,
586 | init: RecyclerView.() -> Unit
587 | ): RecyclerView {
588 | val recyclerView =
589 | if (style != null) RecyclerView(
590 | ContextThemeWrapper(this, style)
591 | ) else RecyclerView(this)
592 | return recyclerView.apply(init)
593 | }
594 |
595 |
596 | /**
597 | * create [FragmentContainerView] instance
598 | * @param style an style int value defined in xml
599 | * @param init set attributes for this view in this lambda
600 | */
601 | inline fun Context.FragmentContainerView(
602 | style: Int? = null,
603 | init: FragmentContainerView.() -> Unit
604 | ): FragmentContainerView {
605 | val fragmentContainerView =
606 | if (style != null) FragmentContainerView(
607 | ContextThemeWrapper(this, style)
608 | ) else FragmentContainerView(this)
609 | return fragmentContainerView.apply(init)
610 | }
611 |
612 | /**
613 | * create [ConstraintLayout] instance within [Fragment]
614 | * @param style an style int value defined in xml
615 | * @param init set attributes for this view in this lambda
616 | */
617 | inline fun Fragment.ConstraintLayout(
618 | style: Int? = null,
619 | init: ConstraintLayout.() -> Unit
620 | ): ConstraintLayout? = context?.let {
621 | if (style != null) ConstraintLayout(
622 | ContextThemeWrapper(it, style)
623 | ) else ConstraintLayout(it)
624 | }?.apply(init)
625 |
626 | /**
627 | * create [LinearLayout] instance within [Fragment]
628 | * @param style an style int value defined in xml
629 | * @param init set attributes for this view in this lambda
630 | */
631 | inline fun Fragment.LinearLayout(
632 | style: Int? = null,
633 | init: LinearLayout.() -> Unit
634 | ): LinearLayout? = context?.let {
635 | if (style != null) LinearLayout(
636 | ContextThemeWrapper(it, style)
637 | ) else LinearLayout(it)
638 | }?.apply(init)
639 |
640 | /**
641 | * create [FrameLayout] instance within [Fragment]
642 | * @param style an style int value defined in xml
643 | * @param init set attributes for this view in this lambda
644 | */
645 | inline fun Fragment.FrameLayout(
646 | style: Int? = null,
647 | init: FrameLayout.() -> Unit
648 | ): FrameLayout? = context?.let {
649 | if (style != null) FrameLayout(
650 | ContextThemeWrapper(it, style)
651 | ) else FrameLayout(it)
652 | }?.apply(init)
653 |
654 | /**
655 | * create [NestedScrollView] instance within [Fragment]
656 | * @param style an style int value defined in xml
657 | * @param init set attributes for this view in this lambda
658 | */
659 | inline fun Fragment.NestedScrollView(
660 | style: Int? = null,
661 | init: FrameLayout.() -> Unit
662 | ): NestedScrollView? = context?.let {
663 | if (style != null) NestedScrollView(
664 | ContextThemeWrapper(it, style)
665 | ) else NestedScrollView(it)
666 | }?.apply(init)
667 |
668 | /**
669 | * create [AppCompatTextView] instance within [Fragment]
670 | * @param style an style int value defined in xml
671 | * @param init set attributes for this view in this lambda
672 | */
673 | inline fun Fragment.TextView(
674 | style: Int? = null,
675 | init: AppCompatTextView.() -> Unit
676 | ): AppCompatTextView? = context?.let {
677 | if (style != null) AppCompatTextView(
678 | ContextThemeWrapper(it, style)
679 | ) else AppCompatTextView(it)
680 | }?.apply(init)
681 |
682 | /**
683 | * create [AppCompatButton] instance within [Fragment]
684 | * @param style an style int value defined in xml
685 | * @param init set attributes for this view in this lambda
686 | */
687 | inline fun Fragment.Button(
688 | style: Int? = null,
689 | init: AppCompatButton.() -> Unit
690 | ): AppCompatButton? = context?.let {
691 | if (style != null) AppCompatButton(
692 | ContextThemeWrapper(it, style)
693 | ) else AppCompatButton(it)
694 | }?.apply(init)
695 |
696 | /**
697 | * create [AppCompatImageView] instance within [Fragment]
698 | * @param style an style int value defined in xml
699 | * @param init set attributes for this view in this lambda
700 | */
701 | inline fun Fragment.ImageView(
702 | style: Int? = null,
703 | init: AppCompatImageView.() -> Unit
704 | ): AppCompatImageView? = context?.let {
705 | if (style != null) AppCompatImageView(
706 | ContextThemeWrapper(it, style)
707 | ) else AppCompatImageView(it)
708 | }?.apply(init)
709 |
710 | /**
711 | * create [View] instance within [Fragment]
712 | * @param style an style int value defined in xml
713 | * @param init set attributes for this view in this lambda
714 | */
715 | inline fun Fragment.View(
716 | style: Int? = null,
717 | init: View.() -> Unit
718 | ): View? = context?.let {
719 | if (style != null) View(
720 | ContextThemeWrapper(it, style)
721 | ) else View(it)
722 | }?.apply(init)
723 |
724 | /**
725 | * create [ViewFlipper] instance within [Fragment]
726 | * @param style an style int value defined in xml
727 | * @param init set attributes for this view in this lambda
728 | */
729 | inline fun Fragment.ViewFlipper(
730 | style: Int? = null,
731 | init: ViewFlipper.() -> Unit
732 | ): ViewFlipper? = context?.let {
733 | if (style != null) ViewFlipper(
734 | ContextThemeWrapper(it, style)
735 | ) else ViewFlipper(it)
736 | }?.apply(init)
737 |
738 | /**
739 | * create [AppCompatEditText] instance within [Fragment]
740 | * @param style an style int value defined in xml
741 | * @param init set attributes for this view in this lambda
742 | */
743 | inline fun Fragment.EditText(
744 | style: Int? = null,
745 | init: AppCompatEditText.() -> Unit
746 | ): AppCompatEditText? = context?.let {
747 | if (style != null) AppCompatEditText(
748 | ContextThemeWrapper(it, style)
749 | ) else AppCompatEditText(it)
750 | }?.apply(init)
751 |
752 | /**
753 | * create [HorizontalScrollView] instance within [Fragment]
754 | * @param style an style int value defined in xml
755 | * @param init set attributes for this view in this lambda
756 | */
757 | inline fun Fragment.HorizontalScrollView(
758 | style: Int? = null,
759 | init: HorizontalScrollView.() -> Unit
760 | ): HorizontalScrollView? = context?.let {
761 | if (style != null) HorizontalScrollView(
762 | ContextThemeWrapper(it, style)
763 | ) else HorizontalScrollView(it)
764 | }?.apply(init)
765 |
766 | /**
767 | * create [ViewPager2] instance within [Fragment]
768 | * @param style an style int value defined in xml
769 | * @param init set attributes for this view in this lambda
770 | */
771 | inline fun Fragment.ViewPager2(
772 | style: Int? = null,
773 | init: ViewPager2.() -> Unit
774 | ): ViewPager2? = context?.let {
775 | if (style != null) ViewPager2(
776 | ContextThemeWrapper(it, style)
777 | ) else ViewPager2(it)
778 | }?.apply(init)
779 |
780 | /**
781 | * create [ViewPager2] instance within [Fragment]
782 | * @param style an style int value defined in xml
783 | * @param init set attributes for this view in this lambda
784 | */
785 | inline fun Fragment.RecyclerView(
786 | style: Int? = null,
787 | init: RecyclerView.() -> Unit
788 | ): RecyclerView? = context?.let {
789 | if (style != null) RecyclerView(
790 | ContextThemeWrapper(it, style)
791 | ) else RecyclerView(it)
792 | }?.apply(init)
793 |
794 |
795 | /**
796 | * create [FragmentContainerView] instance within [Fragment]
797 | * @param style an style int value defined in xml
798 | * @param init set attributes for this view in this lambda
799 | */
800 | inline fun Fragment.FragmentContainerView(
801 | style: Int? = null,
802 | init: FragmentContainerView.() -> Unit
803 | ): FragmentContainerView? = context?.let {
804 | if (style != null) FragmentContainerView(
805 | ContextThemeWrapper(it, style)
806 | ) else FragmentContainerView(it)
807 | }?.apply(init)
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/dsl/LayoutConstants.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.dsl
2 |
3 | import android.graphics.Typeface
4 | import android.graphics.drawable.GradientDrawable
5 | import android.text.InputType
6 | import android.text.TextUtils
7 | import android.view.Gravity
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import android.widget.ImageView
11 | import android.widget.LinearLayout
12 | import androidx.constraintlayout.helper.widget.Flow
13 | import androidx.constraintlayout.widget.Barrier
14 | import androidx.constraintlayout.widget.ConstraintLayout
15 | import androidx.constraintlayout.widget.ConstraintProperties
16 |
17 | val match_parent = ViewGroup.LayoutParams.MATCH_PARENT
18 | val wrap_content = ViewGroup.LayoutParams.WRAP_CONTENT
19 |
20 | val visible = View.VISIBLE
21 | val gone = View.GONE
22 | val invisible = View.INVISIBLE
23 |
24 | val horizontal = LinearLayout.HORIZONTAL
25 | val vertical = LinearLayout.VERTICAL
26 |
27 | val bold = Typeface.BOLD
28 | val normal = Typeface.NORMAL
29 | val italic = Typeface.ITALIC
30 | val bold_italic = Typeface.BOLD_ITALIC
31 |
32 | val gravity_center = Gravity.CENTER
33 | val gravity_left = Gravity.LEFT
34 | val gravity_right = Gravity.RIGHT
35 | val gravity_bottom = Gravity.BOTTOM
36 | val gravity_top = Gravity.TOP
37 | val gravity_center_horizontal = Gravity.CENTER_HORIZONTAL
38 | val gravity_center_vertical = Gravity.CENTER_VERTICAL
39 |
40 | val scale_fit_xy = ImageView.ScaleType.FIT_XY
41 | val scale_center_crop = ImageView.ScaleType.CENTER_CROP
42 | val scale_center = ImageView.ScaleType.CENTER
43 | val scale_center_inside = ImageView.ScaleType.CENTER_INSIDE
44 | val scale_fit_center = ImageView.ScaleType.FIT_CENTER
45 | val scale_fit_end = ImageView.ScaleType.FIT_END
46 | val scale_matrix = ImageView.ScaleType.MATRIX
47 | val scale_fit_start = ImageView.ScaleType.FIT_START
48 |
49 | val constraint_start = ConstraintProperties.START
50 | val constraint_end = ConstraintProperties.END
51 | val constraint_top = ConstraintProperties.TOP
52 | val constraint_bottom = ConstraintProperties.BOTTOM
53 | val constraint_baseline = ConstraintProperties.BASELINE
54 | val constraint_parent = ConstraintProperties.PARENT_ID
55 |
56 | val spread = ConstraintLayout.LayoutParams.CHAIN_SPREAD
57 | val packed = ConstraintLayout.LayoutParams.CHAIN_PACKED
58 | val spread_inside = ConstraintLayout.LayoutParams.CHAIN_SPREAD_INSIDE
59 |
60 | val barrier_left = Barrier.LEFT
61 | val barrier_top = Barrier.TOP
62 | val barrier_right = Barrier.RIGHT
63 | val barrier_bottom = Barrier.BOTTOM
64 | val barrier_start = Barrier.START
65 | val barrier_end = Barrier.END
66 |
67 | val wrap_none = Flow.WRAP_NONE
68 | val wrap_chain = Flow.WRAP_CHAIN
69 | val wrap_aligned = Flow.WRAP_ALIGNED
70 |
71 | val gradient_top_bottom = GradientDrawable.Orientation.TOP_BOTTOM
72 | val gradient_tr_bl = GradientDrawable.Orientation.TR_BL
73 | val gradient_right_left = GradientDrawable.Orientation.RIGHT_LEFT
74 | val gradient_br_tl = GradientDrawable.Orientation.BR_TL
75 | val gradient_bottom_top = GradientDrawable.Orientation.BOTTOM_TOP
76 | val gradient_bl_tr = GradientDrawable.Orientation.BL_TR
77 | val gradient_left_right = GradientDrawable.Orientation.LEFT_RIGHT
78 | val gradient_tl_br = GradientDrawable.Orientation.TL_BR
79 |
80 | val shape_rectangle = GradientDrawable.RECTANGLE
81 | val shape_oval = GradientDrawable.OVAL
82 | val shape_line = GradientDrawable.LINE
83 | val shape_ring = GradientDrawable.RING
84 |
85 | val gradient_type_linear = GradientDrawable.LINEAR_GRADIENT
86 | val gradient_type_radial = GradientDrawable.RADIAL_GRADIENT
87 | val gradient_type_sweep = GradientDrawable.SWEEP_GRADIENT
88 |
89 | val state_enable = android.R.attr.state_enabled
90 | val state_disable = -android.R.attr.state_enabled
91 | val state_pressed = android.R.attr.state_pressed
92 | val state_unpressed = -android.R.attr.state_pressed
93 | val state_focused = android.R.attr.state_focused
94 | val state_unfocused = -android.R.attr.state_focused
95 | val state_selected = android.R.attr.state_selected
96 | val state_unselected = -android.R.attr.state_selected
97 |
98 | val input_type_number = InputType.TYPE_CLASS_NUMBER
99 |
100 | val wrap_mode_chain = Flow.WRAP_CHAIN
101 | val wrap_mode_none = Flow.WRAP_NONE
102 | val wrap_mode_aligned = Flow.WRAP_ALIGNED
103 |
104 | val ellipsize_end = TextUtils.TruncateAt.END
105 | val ellipsize_marquee = TextUtils.TruncateAt.MARQUEE
106 | val ellipsize_middle = TextUtils.TruncateAt.MIDDLE
107 | val ellipsize_start = TextUtils.TruncateAt.START
108 |
109 | val parent_id = "0"
110 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/dsl/LayoutExtra.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.dsl
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.appcompat.view.ContextThemeWrapper
7 | import androidx.fragment.app.Fragment
8 | import taylor.com.views.Selector
9 | import taylor.com.views.LineFeedLayout
10 | import taylor.com.views.PercentLayout
11 | import taylor.com.views.ProgressBar
12 |
13 |
14 | /**
15 | * layout dsl for customized view
16 | */
17 | inline fun ViewGroup.LineFeedLayout(autoAdd: Boolean = true, init: LineFeedLayout.() -> Unit) =
18 | LineFeedLayout(context).apply(init).also { if (autoAdd) addView(it) }
19 |
20 | inline fun Context.LineFeedLayout(init: LineFeedLayout.() -> Unit): LineFeedLayout =
21 | LineFeedLayout(this).apply(init)
22 |
23 | inline fun Fragment.LineFeedLayout(init: LineFeedLayout.() -> Unit) =
24 | context?.let { LineFeedLayout(it).apply(init) }
25 |
26 | inline fun ViewGroup.Selector(autoAdd: Boolean = true, init: Selector.() -> Unit) =
27 | Selector(context).apply(init).also { addView(it) }
28 |
29 | inline fun Context.Selector(init: Selector.() -> Unit): Selector =
30 | Selector(this).apply(init)
31 |
32 | inline fun Fragment.Selector(init: Selector.() -> Unit) =
33 | context?.let { Selector(it).apply(init) }
34 |
35 | inline fun ViewGroup.ProgressBar(autoAdd: Boolean = true, init: ProgressBar.() -> Unit) =
36 | ProgressBar(context).apply(init).also { if (autoAdd) addView(it) }
37 |
38 | inline fun Context.ProgressBar(init: ProgressBar.() -> Unit): ProgressBar =
39 | ProgressBar(this).apply(init)
40 |
41 | inline fun Fragment.ProgressBar(init: ProgressBar.() -> Unit) =
42 | context?.let { ProgressBar(it).apply(init) }
43 |
44 | /**
45 | * create [PercentLayout] instance within a [ViewGroup]
46 | * @param style an style int value defined in xml
47 | * @param autoAdd whether add [PercentLayout] into [ViewGroup] automatically
48 | * @param init set attributes for this view in this lambda
49 | */
50 | inline fun ViewGroup.PercentLayout(
51 | style: Int? = null,
52 | autoAdd: Boolean = true,
53 | init: PercentLayout.() -> Unit
54 | ): PercentLayout {
55 | val percentLayout =
56 | if (style != null) PercentLayout(
57 | ContextThemeWrapper(context, style)
58 | ) else PercentLayout(context)
59 | return percentLayout.apply(init).also { if (autoAdd) addView(it) }
60 | }
61 |
62 | /**
63 | * create [PercentLayout] instance
64 | * @param style an style int value defined in xml
65 | * @param init set attributes for this view in this lambda
66 | */
67 | inline fun Context.PercentLayout(style: Int? = null, init: PercentLayout.() -> Unit): PercentLayout {
68 | val percentLayout =
69 | if (style != null) PercentLayout(
70 | ContextThemeWrapper(this, style)
71 | ) else PercentLayout(this)
72 | return percentLayout.apply(init)
73 | }
74 |
75 | /**
76 | * create [PercentLayout] instance within [Fragment]
77 | * @param style an style int value defined in xml
78 | * @param init set attributes for this view in this lambda
79 | */
80 | inline fun Fragment.PercentLayout(
81 | style: Int? = null,
82 | init: PercentLayout.() -> Unit
83 | ): PercentLayout? = context?.let {
84 | if (style != null) PercentLayout(
85 | ContextThemeWrapper(it, style)
86 | ) else PercentLayout(it)
87 | }?.apply(init)
88 |
89 | inline var LineFeedLayout.horizontal_gap: Int
90 | get() {
91 | return -1
92 | }
93 | set(value) {
94 | horizontalGap = value.dp
95 | }
96 |
97 | inline var LineFeedLayout.vertical_gap: Int
98 | get() {
99 | return -1
100 | }
101 | set(value) {
102 | verticalGap = value.dp
103 | }
104 |
105 |
106 | inline var View.left_percent: Float
107 | get() {
108 | return -1f
109 | }
110 | set(value) {
111 | updateLayoutParams {
112 | leftPercent = value
113 | }
114 | }
115 |
116 | inline var View.top_percent: Float
117 | get() {
118 | return -1f
119 | }
120 | set(value) {
121 | updateLayoutParams {
122 | topPercent = value
123 | }
124 | }
125 |
126 | inline var View.start_to_start_of_percent: String
127 | get() {
128 | return ""
129 | }
130 | set(value) {
131 | updateLayoutParams {
132 | startToStartOf = value.toLayoutId()
133 | }
134 | }
135 |
136 | inline var View.start_to_end_of_percent: String
137 | get() {
138 | return ""
139 | }
140 | set(value) {
141 | updateLayoutParams {
142 | startToEndOf = value.toLayoutId()
143 | }
144 | }
145 |
146 | inline var View.end_to_end_of_percent: String
147 | get() {
148 | return ""
149 | }
150 | set(value) {
151 | updateLayoutParams {
152 | endToEndOf = value.toLayoutId()
153 | }
154 | }
155 |
156 | inline var View.end_to_start_of_percent: String
157 | get() {
158 | return ""
159 | }
160 | set(value) {
161 | updateLayoutParams {
162 | endToStartOf = value.toLayoutId()
163 | }
164 | }
165 |
166 | inline var View.top_to_top_of_percent: String
167 | get() {
168 | return ""
169 | }
170 | set(value) {
171 | updateLayoutParams {
172 | topToTopOf = value.toLayoutId()
173 | }
174 | }
175 |
176 | inline var View.top_to_bottom_of_percent: String
177 | get() {
178 | return ""
179 | }
180 | set(value) {
181 | updateLayoutParams {
182 | topToBottomOf = value.toLayoutId()
183 | }
184 | }
185 |
186 | inline var View.bottom_to_bottom_of_percent: String
187 | get() {
188 | return ""
189 | }
190 | set(value) {
191 | updateLayoutParams {
192 | bottomToBottomOf = value.toLayoutId()
193 | }
194 | }
195 |
196 | inline var View.bottom_to_top_of_percent: String
197 | get() {
198 | return ""
199 | }
200 | set(value) {
201 | updateLayoutParams {
202 | bottomToTopOf = value.toLayoutId()
203 | }
204 | }
205 |
206 | inline var View.center_vertical_of_percent: String
207 | get() {
208 | return ""
209 | }
210 | set(value) {
211 | updateLayoutParams {
212 | centerVerticalOf = value.toLayoutId()
213 | }
214 | }
215 |
216 | inline var View.center_horizontal_of_percent: String
217 | get() {
218 | return ""
219 | }
220 | set(value) {
221 | updateLayoutParams {
222 | centerHorizontalOf = value.toLayoutId()
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/dsl/LayoutHelperFun.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.dsl
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.res.Resources
5 | import android.graphics.Color
6 | import android.graphics.Rect
7 | import android.graphics.drawable.ColorDrawable
8 | import android.os.Build
9 | import android.text.Editable
10 | import android.util.TypedValue
11 | import android.view.*
12 | import android.widget.EditText
13 | import android.widget.TextView
14 | import androidx.appcompat.app.AppCompatActivity
15 | import androidx.constraintlayout.helper.widget.Flow
16 | import androidx.constraintlayout.widget.ConstraintLayout
17 | import androidx.constraintlayout.widget.ConstraintProperties
18 | import androidx.constraintlayout.widget.ConstraintSet
19 | import androidx.coordinatorlayout.widget.ViewGroupUtils
20 | import androidx.fragment.app.DialogFragment
21 | import androidx.lifecycle.LifecycleOwner
22 | import androidx.lifecycle.LiveData
23 | import androidx.recyclerview.widget.RecyclerView
24 | import kotlinx.coroutines.*
25 | import kotlinx.coroutines.channels.Channel
26 | import kotlinx.coroutines.channels.SendChannel
27 | import kotlinx.coroutines.channels.actor
28 | import kotlinx.coroutines.flow.collect
29 | import kotlinx.coroutines.flow.consumeAsFlow
30 | import kotlinx.coroutines.flow.debounce
31 | import kotlin.math.abs
32 |
33 | val Int.dp: Int
34 | get() = TypedValue.applyDimension(
35 | TypedValue.COMPLEX_UNIT_DIP,
36 | this.toFloat(),
37 | Resources.getSystem().displayMetrics
38 | ).toInt()
39 |
40 |
41 | val Float.dp: Float
42 | get() = TypedValue.applyDimension(
43 | TypedValue.COMPLEX_UNIT_DIP,
44 | this,
45 | Resources.getSystem().displayMetrics
46 | )
47 |
48 | val Float.sp: Float
49 | get() = TypedValue.applyDimension(
50 | TypedValue.COMPLEX_UNIT_SP,
51 | this,
52 | Resources.getSystem().displayMetrics
53 | )
54 |
55 | val Number.dp: Int
56 | get() = TypedValue.applyDimension(
57 | TypedValue.COMPLEX_UNIT_DIP,
58 | this.toFloat(),
59 | Resources.getSystem().displayMetrics
60 | ).toInt()
61 |
62 | fun ViewGroup.MarginLayoutParams.toConstraintLayoutParam() =
63 | ConstraintLayout.LayoutParams(width, height).also { it ->
64 | it.topMargin = this.topMargin
65 | it.bottomMargin = this.bottomMargin
66 | it.marginStart = this.marginStart
67 | it.marginEnd = this.marginEnd
68 | }
69 |
70 | /**
71 | * update LayoutParams according to it's type
72 | */
73 | inline fun View.updateLayoutParams(block: T.() -> Unit) {
74 | layoutParams = (layoutParams as? T)?.apply(block) ?: kotlin.run {
75 | val width = layoutParams?.width ?: 0
76 | val height = layoutParams?.height ?: 0
77 | val lp = ViewGroup.LayoutParams(width,height)
78 | new(lp).apply(block)
79 | }
80 | }
81 |
82 | /**
83 | * create a new instance of [T] with [params]
84 | */
85 | inline fun new(vararg params: Any): T =
86 | T::class.java.getDeclaredConstructor(*params.map { it::class.java }.toTypedArray()).also { it.isAccessible = true }.newInstance(*params)
87 |
88 | fun String.toLayoutId(): Int {
89 | var id = hashCode()
90 | if (this == parent_id) id = 0
91 | return abs(id)
92 | }
93 |
94 | fun DialogFragment.fullScreenMode() {
95 | dialog?.window?.apply {
96 | attributes?.apply {
97 | width = WindowManager.LayoutParams.MATCH_PARENT
98 | height = WindowManager.LayoutParams.MATCH_PARENT
99 | }
100 | decorView.setPadding(0, 0, 0, 0)
101 | setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
102 | }
103 | }
104 |
105 | fun View.find(id: String): T? = findViewById(id.toLayoutId())
106 |
107 | fun AppCompatActivity.find(id: String): T? = findViewById(id.toLayoutId())
108 |
109 | @SuppressLint("RestrictedApi")
110 | fun View.expand(dx: Int, dy: Int) {
111 | class MultiTouchDelegate(bound: Rect? = null, delegateView: View) : TouchDelegate(bound, delegateView) {
112 | val delegateViewMap = mutableMapOf()
113 | private var delegateView: View? = null
114 |
115 | override fun onTouchEvent(event: MotionEvent): Boolean {
116 | val x = event.x.toInt()
117 | val y = event.y.toInt()
118 | var handled = false
119 | when (event.actionMasked) {
120 | MotionEvent.ACTION_DOWN -> {
121 | delegateView = findDelegateViewUnder(x, y)
122 | }
123 | MotionEvent.ACTION_CANCEL -> {
124 | delegateView = null
125 | }
126 | }
127 | delegateView?.let {
128 | event.setLocation(it.width / 2f, it.height / 2f)
129 | handled = it.dispatchTouchEvent(event)
130 | }
131 | return handled
132 | }
133 |
134 | private fun findDelegateViewUnder(x: Int, y: Int): View? {
135 | delegateViewMap.forEach { entry -> if (entry.value.contains(x, y)) return entry.key }
136 | return null
137 | }
138 | }
139 |
140 | val parentView = parent as? ViewGroup
141 | parentView ?: return
142 |
143 | if (parentView.touchDelegate == null) parentView.touchDelegate = MultiTouchDelegate(delegateView = this)
144 | post {
145 | val rect = Rect()
146 | ViewGroupUtils.getDescendantRect(parentView, this, rect)
147 | rect.inset(- dx, - dy)
148 | (parentView.touchDelegate as? MultiTouchDelegate)?.delegateViewMap?.put(this, rect)
149 | }
150 | }
151 |
152 | /**
153 | * build a horizontal or vertical chain in [ConstraintLayout]
154 | * [startView] is the starting point of the chain
155 | * [endView] is the endding point of the chain
156 | * [views] is the body of the chain
157 | * [orientation] could be [horizontal] or [vertical]
158 | */
159 | fun ConstraintLayout.buildChain(
160 | startView: View,
161 | views: List,
162 | endView: View?,
163 | orientation: Int,
164 | outMarginStart: Int,
165 | outMarinEnd: Int,
166 | innerMargin: Int
167 | ) {
168 | if (views.isNullOrEmpty()) return
169 | var preView = startView
170 | var startSide = if (orientation == horizontal) constraint_start else constraint_top
171 | var endSide = if (orientation == horizontal) constraint_end else constraint_bottom
172 |
173 | val firstView = views.first()
174 | val isStartViewParent = firstView.isChildOf(startView)
175 | val isEndViewParent = firstView.isChildOf(endView)
176 |
177 | // deal with the first view
178 | ConstraintProperties(firstView)
179 | .connect(
180 | startSide,
181 | if (isStartViewParent) ConstraintProperties.PARENT_ID else preView.id,
182 | if (isStartViewParent) startSide else endSide,
183 | outMarginStart
184 | )
185 | .apply()
186 |
187 | preView = firstView
188 |
189 | (1 until views.size).map { views[it] }.forEach { currentView ->
190 | ConstraintProperties(currentView)
191 | .connect(startSide, preView.id, endSide, innerMargin)
192 | .apply()
193 | ConstraintProperties(preView)
194 | .connect(endSide, currentView.id, startSide, innerMargin)
195 | .apply()
196 | preView = currentView
197 | }
198 |
199 | // deal with the last view
200 | ConstraintProperties(preView)
201 | .connect(
202 | endSide,
203 | if (isEndViewParent) ConstraintProperties.PARENT_ID else endView?.id
204 | ?: ConstraintSet.UNSET,
205 | if (isEndViewParent) endSide else startSide,
206 | outMarinEnd
207 | )
208 | .apply()
209 | }
210 |
211 |
212 | fun ViewGroup.setOnItemClickListener(listener: (View, Int) -> Unit) {
213 | val gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener {
214 | private val touchFrame = Rect()
215 | fun pointToPosition(x: Int, y: Int): Int {
216 | (0 until childCount).map { getChildAt(it) }.forEachIndexed { index, child ->
217 | if (child.visibility == visible && child !is Flow) {
218 | child.getHitRect(touchFrame)
219 | if (touchFrame.contains(x, y)) return index
220 | }
221 | }
222 | return -1
223 | }
224 |
225 | override fun onShowPress(e: MotionEvent?) {
226 | }
227 |
228 | override fun onSingleTapUp(e: MotionEvent?): Boolean {
229 | e ?: return false
230 | pointToPosition(e.x.toInt(), e.y.toInt()).takeIf { it != -1 }?.let { index ->
231 | listener(getChildAt(index), index)
232 | }
233 | return false
234 | }
235 |
236 | override fun onDown(e: MotionEvent?): Boolean {
237 | return true
238 | }
239 |
240 | override fun onFling(
241 | e1: MotionEvent?,
242 | e2: MotionEvent?,
243 | velocityX: Float,
244 | velocityY: Float
245 | ): Boolean {
246 | return false
247 | }
248 |
249 | override fun onScroll(
250 | e1: MotionEvent?,
251 | e2: MotionEvent?,
252 | distanceX: Float,
253 | distanceY: Float
254 | ): Boolean {
255 | return false
256 | }
257 |
258 | override fun onLongPress(e: MotionEvent?) {
259 | }
260 | })
261 |
262 | setOnTouchListener { _, event ->
263 | gestureDetector.onTouchEvent(event)
264 | true
265 | }
266 | }
267 |
268 | fun View.isChildOf(view: View?) = view?.findViewById(this.id) != null
269 |
270 | fun View.observe(liveData: LiveData?, action: (T) -> Unit) {
271 | (context as? LifecycleOwner)?.let { owner ->
272 | liveData?.observe(owner, { action(it) })
273 | }
274 | }
275 |
276 | fun RecyclerView.setOnItemLongClickListener(listener: (View, Int, Float, Float) -> Unit) {
277 | addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
278 | val gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener {
279 | override fun onShowPress(e: MotionEvent?) {
280 | }
281 |
282 | override fun onSingleTapUp(e: MotionEvent?): Boolean {
283 | return false
284 | }
285 |
286 | override fun onDown(e: MotionEvent?): Boolean {
287 | return false
288 | }
289 |
290 | override fun onFling(
291 | e1: MotionEvent?,
292 | e2: MotionEvent?,
293 | velocityX: Float,
294 | velocityY: Float
295 | ): Boolean {
296 | return false
297 | }
298 |
299 | override fun onScroll(
300 | e1: MotionEvent?,
301 | e2: MotionEvent?,
302 | distanceX: Float,
303 | distanceY: Float
304 | ): Boolean {
305 | return false
306 | }
307 |
308 | override fun onLongPress(e: MotionEvent?) {
309 | e?.let {
310 | findChildViewUnder(it.x, it.y)?.let { child ->
311 | val realX = if (child.left >= 0 ) it.x - child.left else it.x
312 | val realY = if (child.top >= 0) it.y - child.top else it.y
313 | listener(
314 | child,
315 | getChildAdapterPosition(child),
316 | realX,
317 | realY
318 | )
319 | }
320 | }
321 | }
322 | })
323 |
324 | override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
325 |
326 | }
327 |
328 | override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
329 | gestureDetector.onTouchEvent(e)
330 | return false
331 | }
332 |
333 | override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
334 | }
335 | })
336 | }
337 |
338 | fun RecyclerView.setOnItemClickListener(listener: (View, Int, Float, Float) -> Boolean) {
339 | addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
340 | val gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener {
341 | override fun onShowPress(e: MotionEvent?) {
342 | }
343 |
344 | override fun onSingleTapUp(e: MotionEvent?): Boolean {
345 | e?.let {
346 | findChildViewUnder(it.x, it.y)?.let { child ->
347 | val realX = if (child.left >= 0 ) it.x - child.left else it.x
348 | val realY = if (child.top >= 0) it.y - child.top else it.y
349 | return listener(
350 | child,
351 | getChildAdapterPosition(child),
352 | realX,
353 | realY
354 | )
355 | }
356 | }
357 | return false
358 | }
359 |
360 | override fun onDown(e: MotionEvent?): Boolean {
361 | return false
362 | }
363 |
364 | override fun onFling(
365 | e1: MotionEvent?,
366 | e2: MotionEvent?,
367 | velocityX: Float,
368 | velocityY: Float
369 | ): Boolean {
370 | return false
371 | }
372 |
373 | override fun onScroll(
374 | e1: MotionEvent?,
375 | e2: MotionEvent?,
376 | distanceX: Float,
377 | distanceY: Float
378 | ): Boolean {
379 | return false
380 | }
381 |
382 | override fun onLongPress(e: MotionEvent?) {
383 | }
384 | })
385 |
386 | override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
387 |
388 | }
389 |
390 | override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
391 | gestureDetector.onTouchEvent(e)
392 | return false
393 | }
394 |
395 | override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
396 | }
397 | })
398 | }
399 |
400 | /**
401 | * get relative position of this [View] relative to [otherView]
402 | */
403 | fun View.getRelativeRectTo(otherView: View): Rect {
404 | val parentRect = Rect().also { otherView.getGlobalVisibleRect(it) }
405 | val childRect = Rect().also { getGlobalVisibleRect(it) }
406 | return childRect.relativeTo(parentRect)
407 | }
408 |
409 | /**
410 | * listen click action for the child view of [RecyclerView]'s item
411 | */
412 | inline fun View.onChildViewClick(
413 | vararg layoutId: String, // the id of the child view of RecyclerView's item
414 | x: Float, // the x coordinate of click point
415 | y: Float,// the y coordinate of click point,
416 | clickAction: ((View?) -> Unit)
417 | ) {
418 | var clickedView: View? = null
419 | layoutId
420 | .map { id ->
421 | find(id)?.takeIf { it.visibility == visible }?.let { view ->
422 | view.getRelativeRectTo(this).also { rect ->
423 | if (rect.contains(x.toInt(), y.toInt())) {
424 | clickedView = view
425 | }
426 | }
427 | } ?: Rect()
428 | }
429 | .fold(Rect()) { init, rect -> init.apply { union(rect) } }
430 | .takeIf { it.contains(x.toInt(), y.toInt()) }
431 | ?.let { clickAction.invoke(clickedView) }
432 | }
433 |
434 | /**
435 | * listen click action for the child view of [RecyclerView]'s item
436 | */
437 | inline fun View.onChildViewClick(
438 | vararg layoutId: Int, // the id of the child view of RecyclerView's item
439 | x: Float, // the x coordinate of click point
440 | y: Float,// the y coordinate of click point,
441 | clickAction: ((View?) -> Unit)
442 | ) {
443 | var clickedView: View? = null
444 | layoutId
445 | .map { id ->
446 | findViewById(id)?.takeIf { it.visibility == visible }?.let { view ->
447 | view.getRelativeRectTo(this).also { rect ->
448 | if (rect.contains(x.toInt(), y.toInt())) {
449 | clickedView = view
450 | }
451 | }
452 | } ?: Rect()
453 | }
454 | .fold(Rect()) { init, rect -> init.apply { union(rect) } }
455 | .takeIf { it.contains(x.toInt(), y.toInt()) }
456 | ?.let { clickAction.invoke(clickedView) }
457 | }
458 |
459 | /**
460 | * a new View.OnClickListener which prevents click shaking
461 | */
462 | fun View.setShakelessClickListener(threshold: Long, onClick: (View) -> Unit) {
463 | class Click(
464 | var view: View? = null,
465 | var clickTime: Long = -1,
466 | var onClick: ((View?) -> Unit)? = null
467 | ) {
468 | fun isShake(click: Click) = abs(clickTime - click.clickTime) < threshold
469 | }
470 |
471 | val mainScope = MainScope()
472 | val clickActor = mainScope.actor(capacity = Channel.UNLIMITED) {
473 | var preClick: Click = Click()
474 | for (click in channel) {
475 | if (!click.isShake(preClick)) {
476 | click.onClick?.invoke(click.view)
477 | }
478 | preClick = click
479 | }
480 | }.autoDispose(this)
481 | setOnClickListener { view ->
482 | mainScope.launch {
483 | clickActor.send(
484 | Click(view, System.currentTimeMillis()) { onClick(view) }
485 | )
486 | }.autoDispose(this)
487 | }
488 | }
489 |
490 | /**
491 | * a debounce listener for [EditText]'s text change event
492 | */
493 | fun EditText.setDebounceListener(timeoutMillis: Long, action: (CharSequence) -> Unit) {
494 | val mainScope = MainScope()
495 | val textChangeActor = mainScope.actor(capacity = Channel.UNLIMITED) {
496 | consumeAsFlow().debounce(timeoutMillis).collect { action.invoke(text) }
497 | }.autoDispose(this)
498 |
499 | onTextChange = textWatcher {
500 | onTextChanged = change@{ text: CharSequence?, _: Int, _: Int, _: Int ->
501 | mainScope.launch {
502 | text.toString().trim().let { textChangeActor.send(it) }
503 | }.autoDispose(this@setDebounceListener)
504 | }
505 | }
506 | }
507 |
508 |
509 | /**
510 | * avoid memory leak for View and activity when activity has finished while coroutine is still running
511 | */
512 | fun Job.autoDispose(view: View?): Job {
513 | view ?: return this
514 |
515 | val listener = object : View.OnAttachStateChangeListener {
516 | override fun onViewDetachedFromWindow(v: View?) {
517 | cancel()
518 | v?.removeOnAttachStateChangeListener(this)
519 | }
520 |
521 | override fun onViewAttachedToWindow(v: View?) = Unit
522 | }
523 |
524 | view.addOnAttachStateChangeListener(listener)
525 | invokeOnCompletion {
526 | view.removeOnAttachStateChangeListener(listener)
527 | }
528 | return this
529 | }
530 |
531 | /**
532 | * avoid memory leak
533 | */
534 | fun SendChannel.autoDispose(view: View?): SendChannel {
535 | view ?: return this
536 |
537 | val isAttached =
538 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && view.isAttachedToWindow || view.windowToken != null
539 | val listener = object : View.OnAttachStateChangeListener {
540 | override fun onViewDetachedFromWindow(v: View?) {
541 | close()
542 | v?.removeOnAttachStateChangeListener(this)
543 | }
544 |
545 | override fun onViewAttachedToWindow(v: View?) = Unit
546 | }
547 |
548 | view.addOnAttachStateChangeListener(listener)
549 | invokeOnClose {
550 | view.removeOnAttachStateChangeListener(listener)
551 | }
552 | return this
553 | }
554 |
555 | val View.viewScope: CoroutineScope
556 | get() {
557 | val key = "ViewScope".hashCode()
558 | var scope = getTag(key) as? CoroutineScope
559 | if (scope == null) {
560 | scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
561 | setTag(key, scope)
562 | val listener = object : View.OnAttachStateChangeListener {
563 | override fun onViewAttachedToWindow(v: View?) {
564 | }
565 |
566 | override fun onViewDetachedFromWindow(v: View?) {
567 | scope.cancel()
568 | }
569 |
570 | }
571 | addOnAttachStateChangeListener(listener)
572 | }
573 | return scope
574 | }
575 |
576 |
577 | /**
578 | * get the relative rect of the [Rect] according to the [otherRect] ,considering the [otherRect]'s left and top is zero
579 | */
580 | fun Rect.relativeTo(otherRect: Rect): Rect {
581 | val relativeLeft = left - otherRect.left
582 | val relativeTop = top - otherRect.top
583 | val relativeRight = relativeLeft + right - left
584 | val relativeBottom = relativeTop + bottom - top
585 | return Rect(relativeLeft, relativeTop, relativeRight, relativeBottom)
586 | }
587 | //
588 |
589 |
590 | //
591 | class TextWatcher(
592 | var beforeTextChanged: (
593 | text: CharSequence?,
594 | start: Int,
595 | count: Int,
596 | after: Int
597 | ) -> Unit = { _, _, _, _ -> },
598 | var onTextChanged: (
599 | text: CharSequence?,
600 | start: Int,
601 | count: Int,
602 | after: Int
603 | ) -> Unit = { _, _, _, _ -> },
604 | var afterTextChanged: (text: Editable?) -> Unit = {}
605 | )
606 |
607 | fun textWatcher(init: TextWatcher.() -> Unit): TextWatcher = TextWatcher().apply(init)
608 |
609 | class EditorActionListener(
610 | var onEditorAction: (
611 | textView: TextView?,
612 | actionId: Int,
613 | keyEvent: KeyEvent?
614 | ) -> Boolean = { _, _, _ -> false }
615 | )
616 |
617 | fun editorAction(init: EditorActionListener.() -> Unit): EditorActionListener =
618 | EditorActionListener().apply(init)
619 |
620 | /**
621 | * helper class for data binding
622 | */
623 | class LiveDataBinder(var liveData: LiveData<*>? = null, var action: ((Any?) -> Unit)? = null)
624 |
625 | fun liveDataBinder(liveData: LiveData<*>?, init: LiveDataBinder.() -> Unit): LiveDataBinder =
626 | LiveDataBinder(liveData).apply(init)
627 |
628 | class Binder(var data: Any?, var action: ((View, Any?) -> Unit)? = null)
629 | //
630 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/dsl/SelectorBuilder.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.dsl
2 |
3 | import android.graphics.drawable.Drawable
4 | import android.graphics.drawable.StateListDrawable
5 |
6 | /**
7 | * helper function for building [StateListDrawable]
8 | */
9 | inline fun selector(init: StateListDrawable.() -> Unit) = StateListDrawable().apply(init)
10 |
11 | inline var StateListDrawable.items: Map
12 | get() {
13 | return emptyMap()
14 | }
15 | set(value) {
16 | value.forEach { entry -> addState(entry.key, entry.value) }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/dsl/ShapeBuilder.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.dsl
2 |
3 | import android.content.res.ColorStateList
4 | import android.graphics.Color
5 | import android.graphics.Rect
6 | import android.graphics.drawable.GradientDrawable
7 | import android.os.Build
8 | import androidx.annotation.RequiresApi
9 |
10 | /**
11 | * helper attribute for building [GradientDrawable]
12 | */
13 | inline var GradientDrawable.solid_color: String
14 | get() {
15 | return ""
16 | }
17 | set(value) {
18 | setColor(Color.parseColor(value))
19 | }
20 |
21 | /**
22 | * color res should be returned by ContextCompat.getColor()
23 | */
24 | inline var GradientDrawable.solid_color_res: Int
25 | get() {
26 | return -1
27 | }
28 | set(value) {
29 | setColor(value)
30 | }
31 |
32 | inline var GradientDrawable.corner_radius: Int
33 | get() {
34 | return -1
35 | }
36 | set(value) {
37 | cornerRadius = value.dp.toFloat()
38 | }
39 |
40 | inline var GradientDrawable.corner_radii: IntArray
41 | get() {
42 | return intArrayOf()
43 | }
44 | set(value) {
45 | cornerRadii = value.map { it.dp.toFloat() }.toFloatArray()
46 | }
47 |
48 | inline var GradientDrawable.gradient_colors: List
49 | get() {
50 | return emptyList()
51 | }
52 | set(value) {
53 | colors = value.map { Color.parseColor(it) }.toIntArray()
54 | }
55 |
56 | /**
57 | * color res should be returned by ContextCompat.getColor()
58 | */
59 | inline var GradientDrawable.gradient_colors_res: List
60 | get() {
61 | return emptyList()
62 | }
63 | set(value) {
64 | colors = value.toIntArray()
65 | }
66 |
67 | inline var GradientDrawable.padding_start: Int
68 | get() {
69 | return -1
70 | }
71 | @RequiresApi(Build.VERSION_CODES.Q)
72 | set(value) {
73 | val paddingRect = Rect().also { getPadding(it) }
74 | setPadding(value.dp, paddingRect.top, paddingRect.right, paddingRect.bottom)
75 | }
76 |
77 | inline var GradientDrawable.padding_end: Int
78 | get() {
79 | return -1
80 | }
81 | @RequiresApi(Build.VERSION_CODES.Q)
82 | set(value) {
83 | val paddingRect = Rect().also { getPadding(it) }
84 | setPadding(paddingRect.left, paddingRect.top, value.dp, paddingRect.bottom)
85 | }
86 |
87 | inline var GradientDrawable.padding_top: Int
88 | get() {
89 | return -1
90 | }
91 | @RequiresApi(Build.VERSION_CODES.Q)
92 | set(value) {
93 | val paddingRect = Rect().also { getPadding(it) }
94 | setPadding(paddingRect.left, value.dp, paddingRect.right, paddingRect.bottom)
95 | }
96 |
97 | inline var GradientDrawable.padding_bottom: Int
98 | get() {
99 | return -1
100 | }
101 | @RequiresApi(Build.VERSION_CODES.Q)
102 | set(value) {
103 | val paddingRect = Rect().also { getPadding(it) }
104 | setPadding(paddingRect.left, paddingRect.top, paddingRect.right, value.dp)
105 | }
106 |
107 | inline var GradientDrawable.strokeAttr: Stroke?
108 | get() {
109 | return null
110 | }
111 | set(value) {
112 | value?.apply { setStroke(width.dp, Color.parseColor(color), dashWidth.dp, dashGap.dp) }
113 | }
114 |
115 | inline var GradientDrawable.strokeAttr_res: Stroke?
116 | get() {
117 | return null
118 | }
119 | set(value) {
120 | value?.apply { setStroke(width.dp, color_res, dashWidth.dp, dashGap.dp) }
121 | }
122 |
123 | inline var GradientDrawable.color_state_list: List>
124 | get() {
125 | return listOf(intArrayOf() to "#000000")
126 | }
127 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
128 | set(value) {
129 | val states = mutableListOf()
130 | val colors = mutableListOf()
131 | value.forEach { pair ->
132 | states.add(pair.first)
133 | colors.add(Color.parseColor(pair.second))
134 | }
135 | color = ColorStateList(states.toTypedArray(), colors.toIntArray())
136 | }
137 |
138 | /**
139 | * color res should be returned by ContextCompat.getColor()
140 | */
141 | inline var GradientDrawable.color_state_list_res: List>
142 | get() {
143 | return listOf(intArrayOf() to 0)
144 | }
145 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
146 | set(value) {
147 | val states = mutableListOf()
148 | val colors = mutableListOf()
149 | value.forEach { pair ->
150 | states.add(pair.first)
151 | colors.add(pair.second)
152 | }
153 | color = ColorStateList(states.toTypedArray(), colors.toIntArray())
154 | }
155 |
156 | /**
157 | * helper function for building [GradientDrawable]
158 | */
159 | inline fun shape(init: GradientDrawable.() -> Unit) = GradientDrawable().apply(init)
160 |
161 | /**
162 | * helper class for set stroke for [GradientDrawable]
163 | * color res should be returned by ContextCompat.getColor()
164 | */
165 | data class Stroke(
166 | var width: Number = 0f,
167 | var color: String = "#000000",
168 | var color_res:Int = 0,
169 | var dashWidth: Float = 0f,
170 | var dashGap: Float = 0f
171 | )
172 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/ui/FirstFragment.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.ui
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Color
5 | import android.os.Bundle
6 | import android.text.Spannable
7 | import android.text.SpannableStringBuilder
8 | import android.text.style.ForegroundColorSpan
9 | import android.util.Log
10 | import android.view.LayoutInflater
11 | import android.view.View
12 | import android.view.ViewGroup
13 | import android.widget.Toast
14 | import androidx.fragment.app.Fragment
15 | import androidx.lifecycle.MutableLiveData
16 | import androidx.recyclerview.widget.LinearLayoutManager
17 | import androidx.recyclerview.widget.RecyclerView
18 | import com.bumptech.glide.Glide
19 | import com.bumptech.glide.request.animation.GlideAnimation
20 | import com.bumptech.glide.request.target.SimpleTarget
21 | import taylor.com.adapter.MyAdapter
22 | import taylor.com.bean.User
23 | import taylor.com.dsl.*
24 |
25 | /**
26 | * show how to use layout dsl in [Fragment]
27 | */
28 | class FirstFragment : Fragment() {
29 |
30 | private val diamondUrl =
31 | "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2571315283,182922750&fm=26&gp=0.jpg"
32 | private val coinUrl =
33 | "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1589731033455&di=4266c84e59efb61a7f82c71b305954db&imgtype=0&src=http%3A%2F%2Fpic.90sjimg.com%2Fdesign%2F00%2F23%2F31%2F57%2F591be2a6807c3.png"
34 |
35 | private val nameLiveData = MutableLiveData()
36 | private val nameColorLiveData = MutableLiveData()
37 | private val avatarLiveData = MutableLiveData()
38 | private val commitLiveData = MutableLiveData()
39 | private val contentLiveData = MutableLiveData().apply { value = false }
40 |
41 | private lateinit var rv: RecyclerView
42 |
43 | private val target = object : SimpleTarget() {
44 | override fun onResourceReady(
45 | resource: Bitmap?,
46 | glideAnimation: GlideAnimation?
47 | ) {
48 | avatarLiveData.value = resource
49 | }
50 | }
51 |
52 | private val rootView by lazy {
53 | ConstraintLayout {
54 | layout_width = match_parent
55 | layout_height = match_parent
56 |
57 | ImageView {
58 | layout_id = "ivBack"
59 | layout_width = 40
60 | layout_height = 40
61 | margin_start = 20
62 | margin_top = 20
63 | src = R.drawable.ic_back_black
64 | start_toStartOf = parent_id
65 | top_toTopOf = parent_id
66 | onClick = onBackClick
67 | }
68 |
69 | TextView(R.style.myTextView) {
70 | layout_id = "tvCommit"
71 | layout_width = wrap_content
72 | layout_height = wrap_content
73 | text = "commit"
74 | gravity = gravity_center
75 | textStyle = bold
76 | align_vertical_to = "ivBack"
77 | center_horizontal = true
78 | padding = 10
79 | onClick = { _: View ->
80 | Toast.makeText(context, "on click", Toast.LENGTH_LONG).show()
81 | }
82 | shape = shape {
83 | corner_radius = 10
84 | shape = shape_rectangle
85 | gradientType = gradient_type_linear
86 | orientation = gradient_left_right
87 | gradient_colors = listOf("#ffff00", "#0000ff")
88 | strokeAttr = Stroke(5, "#000000", dashWidth = 2f, dashGap = 3f)
89 | color_state_list = listOf(
90 | intArrayOf(state_enable) to "#007EFF",
91 | intArrayOf(state_disable) to "#FDB2DA"
92 | )
93 | }
94 | bindLiveData = liveDataBinder(commitLiveData) {
95 | action = {
96 | (it as? Boolean)?.let {
97 | isEnabled = it
98 | }
99 | }
100 | }
101 | }
102 |
103 | ImageView {
104 | layout_width = 40
105 | layout_height = 40
106 | src = R.drawable.ic_member_more
107 | align_vertical_to = "ivBack"
108 | end_toEndOf = parent_id
109 | margin_end = 20
110 | }
111 |
112 | View {
113 | layout_id = "vDivider"
114 | layout_width = match_parent
115 | layout_height = 1
116 | margin_top = 10
117 | background_color = "#eeeeee"
118 | top_toBottomOf = "tvCommit"
119 | }
120 |
121 | Layer {
122 | layout_id = "layer"
123 | layout_width = wrap_content
124 | layout_height = wrap_content
125 | referenceIds = "ivDiamond,tvName,tvContent,ivAvatar,tvTime,tvSub"
126 | background_res = R.drawable.tag_checked_shape
127 | start_toStartOf = "ivDiamond"
128 | top_toTopOf = "ivDiamond"
129 | bottom_toBottomOf = "tvTime"
130 | end_toEndOf = "tvTime"
131 | }
132 |
133 | ImageView {
134 | layout_id = "ivDiamond"
135 | layout_width = 40
136 | layout_height = 40
137 | margin_start = 20
138 | margin_top = 40
139 | src = R.drawable.diamond_tag
140 | start_toStartOf = "ivBack"
141 | top_toBottomOf = "vDivider"
142 | margin_top = 10
143 | onClick = { _: View ->
144 | commitLiveData.postValue(true)
145 | }
146 | }
147 |
148 | TextView {
149 | layout_id = "tvName"
150 | layout_width = wrap_content
151 | layout_height = wrap_content
152 | margin_start = 5
153 | gravity = gravity_center
154 | padding = 10
155 | textSize = 20f
156 | textStyle = bold
157 | align_vertical_to = "ivDiamond"
158 | start_toEndOf = "ivDiamond"
159 | bindLiveData = liveDataBinder(nameLiveData) {
160 | action = {
161 | text = it as CharSequence
162 | }
163 | }
164 | bind = Binder("init title") { _, data ->
165 | text = data.toString()
166 | }
167 |
168 | onClick = { _: View ->
169 | commitLiveData.postValue(false)
170 | }
171 |
172 | }
173 |
174 | TextView {
175 | layout_id = "tvContent"
176 | layout_width = 0
177 | layout_height = wrap_content
178 | margin_top = 5
179 | text = "The changes were merged into release with so many bugs"
180 | textSize = 23f
181 | start_toStartOf = "ivDiamond"
182 | top_toBottomOf = "ivDiamond"
183 | end_toStartOf = "ivAvatar"
184 | bindLiveData = liveDataBinder(contentLiveData) {
185 | action = {
186 | (it as? Boolean)?.let {
187 | isEnabled = it
188 | }
189 | }
190 | }
191 | background_drawable_state_list = listOf(
192 | intArrayOf(state_enable) to shape {
193 | shape = shape_rectangle
194 | corner_radius = 10
195 | solid_color = "#FDB2DA"
196 | },
197 | intArrayOf(state_disable) to shape {
198 | shape = shape_rectangle
199 | corner_radius = 10
200 | solid_color = "#80FDB2DA"
201 | }
202 | )
203 | }
204 |
205 | ImageView {
206 | layout_id = "ivAvatar"
207 | layout_width = 100
208 | layout_height = 100
209 | margin_end = 20
210 | src = R.drawable.user_portrait_gender_female
211 | end_toEndOf = parent_id
212 | start_toEndOf = "tvContent"
213 | top_toTopOf = "tvContent"
214 | onClick = {
215 | contentLiveData.postValue(contentLiveData.value?.not())
216 | }
217 | }
218 |
219 | TextView {
220 | layout_id = "tvSub"
221 | layout_width = wrap_content
222 | layout_height = wrap_content
223 | text = "merge it with mercy"
224 | textColor = "#c4747E8B"
225 | textSize = 18f
226 | start_toStartOf = "ivDiamond"
227 | top_toBottomOf = "tvContent"
228 | }
229 |
230 | TextView {
231 | layout_id = "tvTime"
232 | layout_width = wrap_content
233 | layout_height = wrap_content
234 | margin_top = 20
235 | text = "2020.04.30"
236 | end_toEndOf = "ivAvatar"
237 | top_toBottomOf = "ivAvatar"
238 | }
239 |
240 | TextView {
241 | layout_id = "tvCancel"
242 | layout_width = wrap_content
243 | layout_height = wrap_content
244 | margin_end = 30
245 | background_res = R.drawable.bg_orange_btn
246 | padding_start = 30
247 | padding_top = 10
248 | padding_end = 30
249 | padding_bottom = 10
250 | text = "cancel"
251 | margin_bottom = 20
252 | textSize = 20f
253 | textStyle = bold
254 | bottom_toBottomOf = parent_id
255 | end_toStartOf = "tvOk"
256 | start_toStartOf = parent_id
257 | horizontal_chain_style = packed
258 | onClick = onCancelClick
259 | }
260 |
261 | TextView {
262 | layout_id = "tvOk"
263 | layout_width = wrap_content
264 | layout_height = wrap_content
265 | background_res = R.drawable.bg_orange_btn
266 | padding_start = 30
267 | padding_top = 10
268 | margin_bottom = 20
269 | padding_end = 30
270 | padding_bottom = 10
271 | text = "Ok"
272 | textSize = 20f
273 | textStyle = bold
274 | bottom_toBottomOf = parent_id
275 | end_toEndOf = parent_id
276 | horizontal_chain_style = packed
277 | start_toEndOf = "tvCancel"
278 | }
279 |
280 | rv = RecyclerView {
281 | layout_id = "rvTest"
282 | layout_width = match_parent
283 | layout_height = wrap_content
284 | top_toBottomOf = "tvTime"
285 | bottom_toTopOf = "tvOk"
286 | onItemClick = onListItemClick
287 | margin_top = 10
288 | onItemLongClick = onItemLongClickListener
289 | }
290 |
291 | EditText {
292 | layout_width = match_parent
293 | layout_height = 50
294 | textSize = 20f
295 | background_color = "#00ffff"
296 | top_toBottomOf = "rvTest"
297 | onTextChange = textWatcher {
298 | onTextChanged = { text: CharSequence?, start: Int, count: Int, after: Int ->
299 | Log.v("ttaylor", "tag=text change, FirstFragment.() text=${text}")
300 | }
301 | }
302 | }
303 | }
304 | }
305 |
306 | private val onCancelClick = { v: View ->
307 | nameLiveData.value = "new title"
308 | Unit
309 | }
310 |
311 | private val onItemLongClickListener = { v: View, i: Int, x: Float, y: Float ->
312 | adapter.myBean?.get(i)?.let {
313 | Log.v("ttaylor", "on item(${it.name}) long click ")
314 | }
315 | Unit
316 | }
317 |
318 | private val onListItemClick = { v: View, i: Int, x: Float, y: Float ->
319 | adapter.myBean?.get(i)?.let {
320 | nameLiveData.value = SpannableStringBuilder(it.name).apply {
321 | setSpan(
322 | ForegroundColorSpan(Color.RED),
323 | 0,
324 | it.name.indexOf(" "),
325 | Spannable.SPAN_INCLUSIVE_EXCLUSIVE
326 | )
327 | val color = if (it.gender == 1) "#b300ff00" else "#b3ff00ff"
328 | setSpan(
329 | ForegroundColorSpan(Color.parseColor(color)),
330 | it.name.indexOf(" "),
331 | it.name.lastIndex + 1,
332 | Spannable.SPAN_EXCLUSIVE_INCLUSIVE
333 | )
334 | }
335 |
336 | if (it.gender == 1) Glide.with(context).load(diamondUrl).asBitmap().into(target)
337 | else Glide.with(context).load(coinUrl).asBitmap().into(target)
338 | }
339 | v.onChildViewClick("tvStart", "tvEnd", x = x, y = y) {
340 | Log.v("ttaylor", "tag=adsf, FirstFragment.() on two child clicked")
341 | }
342 | false
343 | }
344 |
345 | private val onBackClick = { v: View ->
346 | activity?.finish()
347 | Unit
348 | }
349 |
350 | private val users = listOf(
351 | User("Taylor Swift", 10),
352 | User("Linda candy", 20, 1),
353 | User("Cindy json", 30, 1),
354 | User("Evian Mary", 40, 1)
355 | )
356 |
357 | private var adapter = MyAdapter(users)
358 |
359 | override fun onCreateView(
360 | inflater: LayoutInflater, container: ViewGroup?,
361 | savedInstanceState: Bundle?
362 | ): View? {
363 | return rootView
364 | }
365 |
366 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
367 | super.onViewCreated(view, savedInstanceState)
368 | rv.layoutManager = LinearLayoutManager(context)
369 | rv.adapter = adapter
370 | }
371 | }
372 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.ui
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import android.widget.TextView
6 |
7 | class MainActivity : AppCompatActivity() {
8 |
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | setContentView(R.layout.activity_main)
12 |
13 | supportFragmentManager.beginTransaction().apply {
14 | add(R.id.container, FirstFragment(), "first")
15 | commit()
16 | }
17 |
18 | findViewById(R.id.tvName).setText("taylor")
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/views/LineFeedLayout.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.views
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)) {
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)) {
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 | ): Boolean {
91 | val childOccupation = lp.leftMargin + child.measuredWidth + lp.rightMargin
92 | return childOccupation > remainWidth
93 | }
94 | }
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/views/OneViewGroup.kt:
--------------------------------------------------------------------------------
1 | package test.taylor.com.taylorcode.ui.one
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import android.os.Build
6 | import android.text.Layout
7 | import android.text.StaticLayout
8 | import android.text.TextPaint
9 | import android.util.AttributeSet
10 | import android.view.GestureDetector
11 | import android.view.MotionEvent
12 | import android.view.ViewGroup
13 | import androidx.annotation.RequiresApi
14 | import androidx.appcompat.widget.AppCompatImageView
15 | import taylor.com.dsl.dp
16 | import taylor.com.dsl.parent_id
17 | import taylor.com.dsl.sp
18 | import taylor.com.dsl.toLayoutId
19 | import kotlin.math.min
20 |
21 | @RequiresApi(Build.VERSION_CODES.M)
22 | class OneViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ViewGroup(context, attrs, defStyleAttr) {
23 |
24 | private val drawableMap = HashMap()
25 | private val drawables = mutableListOf()
26 | private var gestureDetector: GestureDetector? = null
27 |
28 | fun addDrawable(drawable: Drawable) {
29 | drawables.add(drawable)
30 | drawableMap[drawable.layoutId] = drawable
31 | }
32 |
33 | /**
34 | * find [Drawable] by id
35 | */
36 | fun findDrawable(id: String): T? = drawableMap[id.toLayoutId()] as? T
37 |
38 | /**
39 | * set item click listener for [OneViewGroup] whick detect child [Drawable]'s click event
40 | */
41 | fun setOnItemClickListener(onItemClickListener: (String) -> Unit) {
42 | gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener {
43 | override fun onShowPress(e: MotionEvent?) {
44 | }
45 |
46 | override fun onSingleTapUp(e: MotionEvent?): Boolean {
47 | e?.let {
48 | findDrawableUnder(e.x, e.y)?.let { onItemClickListener.invoke(it.layoutIdString) }
49 | }
50 | return true
51 | }
52 |
53 | override fun onDown(e: MotionEvent?): Boolean {
54 | return true
55 | }
56 |
57 | override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
58 | return false
59 | }
60 |
61 | override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
62 | return false
63 | }
64 |
65 | override fun onLongPress(e: MotionEvent?) {
66 | }
67 | })
68 | }
69 |
70 | /**
71 | * find child [Drawable] according to coordinate
72 | */
73 | private fun findDrawableUnder(x: Float, y: Float): Drawable? {
74 | drawables.forEach {
75 | if (it.rect.contains(x.toInt(), y.toInt())) {
76 | return it
77 | }
78 | }
79 | return null
80 | }
81 |
82 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
83 | measureChildren(widthMeasureSpec, heightMeasureSpec)
84 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
85 | drawables.forEach { it.doMeasure(widthMeasureSpec, heightMeasureSpec) }
86 | }
87 |
88 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
89 | val parentWidth = right - left
90 | val parentHeight = bottom - top
91 | drawables.forEach {
92 | val left = getChildLeft(it, parentWidth)
93 | val top = getChildTop(it, parentHeight)
94 | it.doLayout(changed, left, top, left + it.layoutMeasuredWidth, top + it.layoutMeasuredHeight)
95 | }
96 | }
97 |
98 | override fun onDraw(canvas: Canvas?) {
99 | super.onDraw(canvas)
100 | drawables.forEach { it.doDraw(canvas) }
101 | }
102 |
103 | override fun onTouchEvent(event: MotionEvent?): Boolean {
104 | return gestureDetector?.onTouchEvent(event) ?: super.onTouchEvent(event)
105 | }
106 |
107 | /**
108 | * get the top of [drawable] relative to the [OneViewGroup]
109 | */
110 | private fun getChildTop(drawable: Drawable, parentHeight: Int): Int {
111 | val parentId = parent_id.toLayoutId()
112 | return when {
113 | drawable.topPercent != -1f -> (parentHeight * drawable.topPercent).toInt()
114 | drawable.centerVerticalOf != -1 -> {
115 | if (drawable.centerVerticalOf == parentId) {
116 | (parentHeight - drawable.layoutMeasuredHeight) / 2
117 | } else {
118 | (drawableMap[drawable.centerVerticalOf]?.let { it.layoutTop + (it.layoutBottom - it.layoutTop) / 2 } ?: 0) - drawable.layoutMeasuredHeight / 2
119 | }
120 | }
121 | drawable.topToBottomOf != -1 -> {
122 | val b = if (drawable.topToBottomOf == parentId) bottom else drawableMap[drawable.topToBottomOf]?.layoutBottom ?: 0
123 | (b + drawable.layoutTopMargin)
124 | }
125 | drawable.topToTopOf != -1 -> {
126 | val t = if (drawable.topToTopOf == parentId) top else drawableMap[drawable.topToTopOf]?.layoutTop ?: 0
127 | (t + drawable.layoutTopMargin)
128 | }
129 | drawable.bottomToTopOf != -1 -> {
130 | val t = if (drawable.bottomToTopOf == parentId) top else drawableMap[drawable.bottomToTopOf]?.layoutTop ?: 0
131 | (t - drawable.layoutBottomMargin) - drawable.layoutMeasuredHeight
132 | }
133 | drawable.bottomToBottomOf != -1 -> {
134 | val b = if (drawable.bottomToBottomOf == parentId) bottom else drawableMap[drawable.bottomToBottomOf]?.layoutBottom ?: 0
135 | (b - drawable.layoutBottomMargin) - drawable.layoutMeasuredHeight
136 | }
137 | else -> 0
138 | }
139 | }
140 |
141 | /**
142 | * get the left of [drawable] relative to the [OneViewGroup]
143 | */
144 | private fun getChildLeft(drawable: Drawable, parentWidth: Int): Int {
145 | val parentId = parent_id.toLayoutId()
146 | return when {
147 | drawable.leftPercent != -1f -> (parentWidth * drawable.leftPercent).toInt()
148 | drawable.centerHorizontalOf != -1 -> {
149 | if (drawable.centerHorizontalOf == parentId) {
150 | (parentWidth - drawable.layoutMeasuredWidth) / 2
151 | } else {
152 | (drawableMap[drawable.centerHorizontalOf]?.let { it.layoutLeft + (it.layoutRight - it.layoutLeft) / 2 } ?: 0) - drawable.layoutMeasuredWidth / 2
153 | }
154 | }
155 | drawable.startToEndOf != -1 -> {
156 | val r = if (drawable.startToEndOf == parentId) right else drawableMap[drawable.startToEndOf]?.layoutRight ?: 0
157 | (r + drawable.layoutLeftMargin)
158 | }
159 | drawable.startToStartOf != -1 -> {
160 | val l = if (drawable.startToStartOf == parentId) left else drawableMap[drawable.startToStartOf]?.layoutLeft ?: 0
161 | (l + drawable.layoutLeftMargin)
162 | }
163 | drawable.endToStartOf != -1 -> {
164 | val l = if (drawable.endToStartOf == parentId) left else drawableMap[drawable.endToStartOf]?.layoutLeft ?: 0
165 | (l - drawable.layoutRightMargin) - drawable.layoutMeasuredWidth
166 | }
167 | drawable.endToEndOf != -1 -> {
168 | val r = if (drawable.endToEndOf == parentId) right else drawableMap[drawable.endToEndOf]?.layoutRight ?: 0
169 | (r - drawable.layoutRightMargin) - drawable.layoutMeasuredWidth
170 | }
171 | else -> 0
172 | }
173 | }
174 |
175 | /**
176 | * anything could be draw in [OneViewGroup]
177 | */
178 | interface Drawable {
179 | /**
180 | * the measured dimension of [Drawable]
181 | */
182 | var layoutMeasuredWidth: Int
183 | var layoutMeasuredHeight: Int
184 |
185 | /**
186 | * the frame rect of [Drawable]
187 | */
188 | var layoutLeft: Int
189 | var layoutRight: Int
190 | var layoutTop: Int
191 | var layoutBottom: Int
192 | val rect: Rect
193 | get() = Rect(layoutLeft, layoutTop, layoutRight, layoutBottom)
194 |
195 | /**
196 | * the unique id of this [Drawable] in int
197 | */
198 | var layoutId: Int
199 |
200 | /**
201 | * the unique id of this [Drawable] in string
202 | */
203 | var layoutIdString: String
204 |
205 | /**
206 | * the relative position of this [Drawable] to another
207 | */
208 | var leftPercent: Float
209 | var topPercent: Float
210 | var startToStartOf: Int
211 | var startToEndOf: Int
212 | var endToEndOf: Int
213 | var endToStartOf: Int
214 | var topToTopOf: Int
215 | var topToBottomOf: Int
216 | var bottomToTopOf: Int
217 | var bottomToBottomOf: Int
218 | var centerHorizontalOf: Int
219 | var centerVerticalOf: Int
220 |
221 | /**
222 | * dimension of [Drawable]
223 | */
224 | var layoutWidth: Int
225 | var layoutHeight: Int
226 |
227 | /**
228 | * inner padding of [Drawable]
229 | */
230 | var layoutPaddingStart: Int
231 | var layoutPaddingEnd: Int
232 | var layoutPaddingTop: Int
233 | var layoutPaddingBottom: Int
234 |
235 | /**
236 | * out margin of [Drawable]
237 | */
238 | var layoutTopMargin: Int
239 | var layoutBottomMargin: Int
240 | var layoutLeftMargin: Int
241 | var layoutRightMargin: Int
242 |
243 | /**
244 | * the the frame rect of [Drawable] is set after this function
245 | */
246 | fun setRect(left: Int, top: Int, right: Int, bottom: Int) {
247 | this.layoutLeft = left
248 | this.layoutRight = right
249 | this.layoutTop = top
250 | this.layoutBottom = bottom
251 | }
252 |
253 | /**
254 | * the measured width and height of [Drawable] is set after this function
255 | */
256 | fun setDimension(width: Int, height: Int) {
257 | this.layoutMeasuredWidth = width
258 | this.layoutMeasuredHeight = height
259 | }
260 |
261 | fun doMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
262 | fun doLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int)
263 | fun doDraw(canvas: Canvas?)
264 | }
265 | }
266 |
267 |
268 | ///**
269 | // * one kind of drawable shows image
270 | // */
271 | //class Image : OneViewGroup.Drawable() {
272 | // override fun measure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
273 | // }
274 | //
275 | // override fun draw(canvas: Canvas?) {
276 | // }
277 | //}
278 |
279 | //
280 | /**
281 | * one kind of [OneViewGroup.Drawable] shows image
282 | * and it is a special [ImageView] implemented [OneViewGroup.Drawable]
283 | */
284 | class ImageDrawable @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : AppCompatImageView(context, attrs, defStyleAttr), OneViewGroup.Drawable {
285 | override var leftPercent: Float = -1f
286 | override var topPercent: Float = -1f
287 | override var startToStartOf: Int = -1
288 | override var startToEndOf: Int = -1
289 | override var endToEndOf: Int = -1
290 | override var endToStartOf: Int = -1
291 | override var topToTopOf: Int = -1
292 | override var topToBottomOf: Int = -1
293 | override var bottomToTopOf: Int = -1
294 | override var bottomToBottomOf: Int = -1
295 | override var centerHorizontalOf: Int = -1
296 | override var centerVerticalOf: Int = -1
297 | override var layoutWidth: Int = 0
298 | override var layoutHeight: Int = 0
299 |
300 | override var layoutMeasuredWidth: Int = 0
301 | get() = measuredWidth
302 | override var layoutMeasuredHeight: Int = 0
303 | get() = measuredHeight
304 | override var layoutLeft: Int = 0
305 | get() = left
306 | override var layoutRight: Int = 0
307 | get() = right
308 | override var layoutTop: Int = 0
309 | get() = top
310 | override var layoutBottom: Int = 0
311 | get() = bottom
312 | override var layoutId: Int = 0
313 | get() = id
314 | override var layoutIdString: String = ""
315 | override var layoutPaddingStart: Int = 0
316 | get() = paddingStart
317 | override var layoutPaddingEnd: Int = 0
318 | get() = paddingEnd
319 | override var layoutPaddingTop: Int = 0
320 | get() = paddingTop
321 | override var layoutPaddingBottom: Int = 0
322 | get() = paddingBottom
323 | override var layoutTopMargin: Int = 0
324 | get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin ?: 0
325 | override var layoutBottomMargin: Int = 0
326 | get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.bottomMargin ?: 0
327 | override var layoutLeftMargin: Int = 0
328 | get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.leftMargin ?: 0
329 | override var layoutRightMargin: Int = 0
330 | get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.rightMargin ?: 0
331 |
332 | override fun doMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
333 | }
334 |
335 | override fun doLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
336 | layout(left, top, right, bottom)
337 | }
338 |
339 | override fun doDraw(canvas: Canvas?) {
340 | }
341 | }
342 |
343 | /**
344 | * one kind of [OneViewGroup.Drawable] shows text
345 | */
346 | class Text : OneViewGroup.Drawable {
347 | private var textPaint: TextPaint? = null
348 | private var staticLayout: StaticLayout? = null
349 |
350 | var text: CharSequence = ""
351 | var textSize: Float = 0f
352 | var textColor: Int = Color.parseColor("#ffffff")
353 | var spaceAdd: Float = 0f
354 | var spaceMulti: Float = 1.0f
355 | var maxLines = 1
356 | var maxWidth = 0
357 | var gravity: Layout.Alignment = Layout.Alignment.ALIGN_NORMAL
358 | var shapePaint: Paint? = null
359 | var drawableShape: Shape? = null
360 | set(value) {
361 | field = value
362 | shapePaint = Paint().apply {
363 | isAntiAlias = true
364 | style = Paint.Style.FILL
365 | color = Color.parseColor(value?.color)
366 | }
367 | }
368 | override var layoutMeasuredWidth: Int = 0
369 | override var layoutMeasuredHeight: Int = 0
370 | override var layoutLeft: Int = 0
371 | override var layoutRight: Int = 0
372 | override var layoutTop: Int = 0
373 | override var layoutBottom: Int = 0
374 | override var layoutId: Int = 0
375 | override var layoutIdString: String = ""
376 | override var leftPercent: Float = -1f
377 | override var topPercent: Float = -1f
378 | override var startToStartOf: Int = -1
379 | override var startToEndOf: Int = -1
380 | override var endToEndOf: Int = -1
381 | override var endToStartOf: Int = -1
382 | override var topToTopOf: Int = -1
383 | override var topToBottomOf: Int = -1
384 | override var bottomToTopOf: Int = -1
385 | override var bottomToBottomOf: Int = -1
386 | override var centerHorizontalOf: Int = -1
387 | override var centerVerticalOf: Int = -1
388 | override var layoutWidth: Int = 0
389 | override var layoutHeight: Int = 0
390 | override var layoutPaddingStart: Int = 0
391 | override var layoutPaddingEnd: Int = 0
392 | override var layoutPaddingTop: Int = 0
393 | override var layoutPaddingBottom: Int = 0
394 | override var layoutTopMargin: Int = 0
395 | override var layoutBottomMargin: Int = 0
396 | override var layoutLeftMargin: Int = 0
397 | override var layoutRightMargin: Int = 0
398 |
399 | @RequiresApi(Build.VERSION_CODES.M)
400 | override fun doMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
401 | if (textPaint == null) {
402 | textPaint = TextPaint().apply {
403 | isAntiAlias = true
404 | textSize = this@Text.textSize
405 | color = textColor
406 | }
407 | }
408 |
409 | val measureWidth = if (layoutWidth != 0) layoutWidth else min(textPaint!!.measureText(text.toString()).toInt(), maxWidth)
410 | if (staticLayout == null) {
411 | staticLayout = StaticLayout.Builder.obtain(text, 0, text.length, textPaint!!, measureWidth)
412 | .setAlignment(gravity)
413 | .setLineSpacing(spaceAdd, spaceMulti)
414 | .setIncludePad(false)
415 | .setMaxLines(maxLines).build()
416 | }
417 |
418 | val measureHeight = staticLayout!!.height
419 | setDimension(measureWidth + layoutPaddingEnd + layoutPaddingStart, measureHeight + layoutPaddingTop + layoutPaddingBottom)
420 | }
421 |
422 | override fun doLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
423 | setRect(left, top, right, bottom)
424 | }
425 |
426 | override fun doDraw(canvas: Canvas?) {
427 | canvas?.save()
428 | canvas?.translate(layoutLeft.toFloat(), layoutTop.toFloat())
429 | drawBackground(canvas)
430 | canvas?.translate(layoutPaddingStart.toFloat(), layoutPaddingTop.toFloat())
431 | staticLayout?.draw(canvas)
432 | canvas?.restore()
433 | }
434 |
435 | private fun drawBackground(canvas: Canvas?) {
436 | if (drawableShape == null) return
437 | val _shape = drawableShape!!
438 | if (_shape.radius != 0f) {
439 | canvas?.drawRoundRect(RectF(0f, 0f, layoutMeasuredWidth.toFloat(), layoutMeasuredHeight.toFloat()), _shape.radius, _shape.radius, shapePaint!!)
440 | } else if (_shape.corners != null) {
441 | _shape.path!!.apply {
442 | addRoundRect(
443 | RectF(0f, 0f, layoutMeasuredWidth.toFloat(), layoutMeasuredHeight.toFloat()),
444 | _shape.corners!!.radii,
445 | Path.Direction.CCW
446 | )
447 | }
448 | canvas?.drawPath(_shape.path!!, shapePaint!!)
449 | }
450 | }
451 | }
452 | //
453 |
454 | /**
455 | * a round rect shows in background
456 | */
457 | class Shape {
458 | var color: String? = null
459 | var colors: List? = null
460 | var radius: Float = 0f
461 | internal var path: Path? = null
462 | var corners: Corners? = null
463 | set(value) {
464 | field = value
465 | path = Path()
466 | }
467 |
468 | class Corners(
469 | var leftTopRx: Float = 0f,
470 | var leftTopRy: Float = 0f,
471 | var leftBottomRx: Float = 0f,
472 | var LeftBottomRy: Float = 0f,
473 | var rightTopRx: Float = 0f,
474 | var rightTopRy: Float = 0f,
475 | var rightBottomRx: Float = 0f,
476 | var rightBottomRy: Float = 0f
477 | ) {
478 | val radii: FloatArray
479 | get() = floatArrayOf(leftTopRx, leftTopRy, rightTopRx, rightTopRy, rightBottomRx, rightBottomRy, leftBottomRx, LeftBottomRy)
480 | }
481 | }
482 |
483 | @RequiresApi(Build.VERSION_CODES.M)
484 | inline fun OneViewGroup.text(init: Text.() -> Unit) = Text().apply(init).also { addDrawable(it) }
485 |
486 | @RequiresApi(Build.VERSION_CODES.M)
487 | inline fun OneViewGroup.image(init: ImageDrawable.() -> Unit) = ImageDrawable(context).apply(init).also {
488 | addView(it)
489 | addDrawable(it)
490 | }
491 |
492 |
493 | //
494 | fun OneViewGroup.drawableShape(init: Shape.() -> Unit): Shape = Shape().apply(init)
495 |
496 | fun Shape.corners(init: Shape.Corners.() -> Unit): Shape.Corners = Shape.Corners().apply(init)
497 |
498 | val gravity_center = Layout.Alignment.ALIGN_CENTER
499 | val gravity_left = Layout.Alignment.ALIGN_NORMAL
500 |
501 | inline var OneViewGroup.Drawable.drawable_top_margin: Int
502 | get() {
503 | return 0
504 | }
505 | set(value) {
506 | layoutTopMargin = value.dp
507 | }
508 |
509 | inline var OneViewGroup.Drawable.drawable_bottom_margin: Int
510 | get() {
511 | return 0
512 | }
513 | set(value) {
514 | layoutBottomMargin = value.dp
515 | }
516 |
517 | inline var OneViewGroup.Drawable.drawable_left_margin: Int
518 | get() {
519 | return 0
520 | }
521 | set(value) {
522 | layoutLeftMargin = value.dp
523 | }
524 |
525 | inline var OneViewGroup.Drawable.drawable_right_margin: Int
526 | get() {
527 | return 0
528 | }
529 | set(value) {
530 | layoutRightMargin = value.dp
531 | }
532 |
533 | inline var OneViewGroup.Drawable.drawable_padding_start: Int
534 | get() {
535 | return 0
536 | }
537 | set(value) {
538 | layoutPaddingStart = value.dp
539 | }
540 |
541 | inline var OneViewGroup.Drawable.drawable_padding_end: Int
542 | get() {
543 | return 0
544 | }
545 | set(value) {
546 | layoutPaddingEnd = value.dp
547 | }
548 |
549 | inline var OneViewGroup.Drawable.drawable_padding_top: Int
550 | get() {
551 | return 0
552 | }
553 | set(value) {
554 | layoutPaddingTop = value.dp
555 | }
556 |
557 | inline var OneViewGroup.Drawable.drawable_padding_bottom: Int
558 | get() {
559 | return 0
560 | }
561 | set(value) {
562 | layoutPaddingBottom = value.dp
563 | }
564 |
565 | inline var OneViewGroup.Drawable.drawable_layout_id: String
566 | get() {
567 | return ""
568 | }
569 | set(value) {
570 | layoutIdString = value
571 | layoutId = value.toLayoutId()
572 | }
573 |
574 | inline var OneViewGroup.Drawable.drawable_start_to_start_of: String
575 | get() {
576 | return ""
577 | }
578 | set(value) {
579 | startToStartOf = value.toLayoutId()
580 | }
581 |
582 | inline var OneViewGroup.Drawable.drawable_start_to_end_of: String
583 | get() {
584 | return ""
585 | }
586 | set(value) {
587 | startToEndOf = value.toLayoutId()
588 | }
589 |
590 | inline var OneViewGroup.Drawable.drawable_end_to_start_of: String
591 | get() {
592 | return ""
593 | }
594 | set(value) {
595 | endToStartOf = value.toLayoutId()
596 | }
597 |
598 | inline var OneViewGroup.Drawable.drawable_end_to_end_of: String
599 | get() {
600 | return ""
601 | }
602 | set(value) {
603 | endToEndOf = value.toLayoutId()
604 | }
605 |
606 | inline var OneViewGroup.Drawable.drawable_top_to_top_of: String
607 | get() {
608 | return ""
609 | }
610 | set(value) {
611 | topToTopOf = value.toLayoutId()
612 | }
613 |
614 | inline var OneViewGroup.Drawable.drawable_top_to_bottom_of: String
615 | get() {
616 | return ""
617 | }
618 | set(value) {
619 | topToBottomOf = value.toLayoutId()
620 | }
621 |
622 | inline var OneViewGroup.Drawable.drawable_bottom_to_top_of: String
623 | get() {
624 | return ""
625 | }
626 | set(value) {
627 | bottomToTopOf = value.toLayoutId()
628 | }
629 |
630 | inline var OneViewGroup.Drawable.drawable_bottom_to_bottom_of: String
631 | get() {
632 | return ""
633 | }
634 | set(value) {
635 | bottomToBottomOf = value.toLayoutId()
636 | }
637 |
638 | inline var OneViewGroup.Drawable.drawable_center_horizontal_of: String
639 | get() {
640 | return ""
641 | }
642 | set(value) {
643 | centerHorizontalOf = value.toLayoutId()
644 | }
645 |
646 | inline var OneViewGroup.Drawable.drawable_center_vertical_of: String
647 | get() {
648 | return ""
649 | }
650 | set(value) {
651 | centerVerticalOf = value.toLayoutId()
652 | }
653 |
654 | inline var Text.drawable_gravity: Layout.Alignment
655 | get() {
656 | return Layout.Alignment.ALIGN_NORMAL
657 | }
658 | set(value) {
659 | gravity = value
660 | }
661 |
662 | inline var Text.drawable_text_size: Float
663 | get() {
664 | return 0f
665 | }
666 | set(value) {
667 | textSize = value.sp
668 | }
669 |
670 | inline var Text.drawable_max_width: Int
671 | get() {
672 | return 0
673 | }
674 | set(value) {
675 | maxWidth = value.dp
676 | }
677 |
678 | inline var Text.drawable_layout_width: Int
679 | get() {
680 | return 0
681 | }
682 | set(value) {
683 | layoutWidth = value.dp
684 | }
685 |
686 | inline var Text.drawable_layout_height: Int
687 | get() {
688 | return 0
689 | }
690 | set(value) {
691 | layoutHeight = value.dp
692 | }
693 |
694 | inline var Text.drawable_text_color: String
695 | get() {
696 | return ""
697 | }
698 | set(value) {
699 | textColor = Color.parseColor(value)
700 | }
701 |
702 | inline var Text.drawable_text: CharSequence
703 | get() {
704 | return ""
705 | }
706 | set(value) {
707 | text = value
708 | }
709 |
710 | inline var Text.drawable_max_lines: Int
711 | get() {
712 | return 1
713 | }
714 | set(value) {
715 | maxLines = value
716 | }
717 |
718 | inline var Text.drawable_shape: Shape
719 | get() {
720 | return Shape()
721 | }
722 | set(value) {
723 | drawableShape = value
724 | }
725 |
726 |
727 | inline var Shape.radius: Float
728 | get() {
729 | return 0f
730 | }
731 | set(value) {
732 | radius = value.dp
733 | }
734 | //
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/views/PercentLayout.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.views
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.util.SparseArray
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import taylor.com.dsl.parent_id
9 | import taylor.com.dsl.toLayoutId
10 |
11 | /**
12 | * the child in [PercentLayout] use [LayoutParam.leftPercent] and [LayoutParam.topPercent] to locate itself, which is a percentage of the width and height of [PercentLayout].
13 | *
14 | * [PercentLayout] must have a specific width or height, or the children view of it will have no idea where to locate.
15 | */
16 | class PercentLayout
17 | @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ViewGroup(context, attrs, defStyleAttr) {
18 |
19 | private val childMap = SparseArray()
20 |
21 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
22 | measureChildren(widthMeasureSpec, heightMeasureSpec)
23 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
24 | }
25 |
26 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
27 | val parentWidth = right - left
28 | val parentHeight = bottom - top
29 | (0 until childCount).map { getChildAt(it) }.forEach { child ->
30 | val lp = child.layoutParams as LayoutParam
31 | val childLeft = getChildLeft(lp, parentWidth, child)
32 | val childTop = getChildTop(lp, parentHeight, child)
33 | child.layout(childLeft, childTop, childLeft + child.measuredWidth, childTop + child.measuredHeight)
34 | }
35 | }
36 |
37 | private fun getChildTop(lp: LayoutParam, parentHeight: Int, child: View): Int {
38 | val parentId = parent_id.toLayoutId()
39 | return when {
40 | lp.topPercent != -1f -> (parentHeight * lp.topPercent).toInt()
41 | lp.centerVerticalOf != -1 -> {
42 | if (lp.centerVerticalOf == parentId) {
43 | (parentHeight - child.measuredHeight) / 2
44 | } else {
45 | (childMap.get(lp.centerVerticalOf)?.let { it.top + (it.bottom - it.top) / 2 } ?: 0) - child.measuredHeight / 2
46 | }
47 | }
48 | lp.topToBottomOf != -1 -> {
49 | val b = if (lp.topToBottomOf == parentId) bottom else childMap.get(lp.topToBottomOf)?.bottom ?: 0
50 | (b + lp.topMargin)
51 | }
52 | lp.topToTopOf != -1 -> {
53 | val t = if (lp.topToTopOf == parentId) top else childMap.get(lp.topToTopOf)?.top ?: 0
54 | (t + lp.topMargin)
55 | }
56 | lp.bottomToTopOf != -1 -> {
57 | val t = if (lp.bottomToTopOf == parentId) top else childMap.get(lp.bottomToTopOf)?.top ?: 0
58 | (t - lp.bottomMargin) - child.measuredHeight
59 | }
60 | lp.bottomToBottomOf != -1 -> {
61 | val b = if (lp.bottomToBottomOf == parentId) bottom else childMap.get(lp.bottomToBottomOf)?.bottom ?: 0
62 | (b - lp.bottomMargin) - child.measuredHeight
63 | }
64 | else -> 0
65 | }
66 | }
67 |
68 | private fun getChildLeft(lp: LayoutParam, parentWidth: Int, child: View): Int {
69 | val parentId = parent_id.toLayoutId()
70 | return when {
71 | lp.leftPercent != -1f -> (parentWidth * lp.leftPercent).toInt()
72 | lp.centerHorizontalOf != -1 -> {
73 | if (lp.centerHorizontalOf == parentId) {
74 | (parentWidth - child.measuredWidth) / 2
75 | } else {
76 | (childMap.get(lp.centerHorizontalOf)?.let { it.left + (it.right - it.left) / 2 } ?: 0) - child.measuredWidth / 2
77 | }
78 | }
79 | lp.startToEndOf != -1 -> {
80 | val r = if (lp.startToEndOf == parentId) right else childMap.get(lp.startToEndOf)?.right ?: 0
81 | (r + lp.marginStart)
82 | }
83 | lp.startToStartOf != -1 -> {
84 | val l = if (lp.startToStartOf == parentId) left else childMap.get(lp.startToStartOf)?.left ?: 0
85 | (l + lp.marginStart)
86 | }
87 | lp.endToStartOf != -1 -> {
88 | val l = if (lp.endToStartOf == parentId) left else childMap.get(lp.endToStartOf)?.left ?: 0
89 | (l - lp.marginEnd) - child.measuredWidth
90 | }
91 | lp.endToEndOf != -1 -> {
92 | val r = if (lp.endToEndOf == parentId) right else childMap.get(lp.endToEndOf)?.right ?: 0
93 | (r - lp.marginEnd) - child.measuredWidth
94 | }
95 | else -> 0
96 | }
97 | }
98 |
99 | override fun onViewAdded(child: View?) {
100 | super.onViewAdded(child)
101 | child?.let { childMap.put(it.id, it) }
102 | }
103 |
104 | override fun onViewRemoved(child: View?) {
105 | super.onViewRemoved(child)
106 | child?.let { childMap.remove(it.id) }
107 | }
108 |
109 | class LayoutParam(source: ViewGroup.LayoutParams?) : MarginLayoutParams(source) {
110 | var leftPercent: Float = -1f
111 | var topPercent: Float = -1f
112 | var startToStartOf: Int = -1
113 | var startToEndOf: Int = -1
114 | var endToEndOf: Int = -1
115 | var endToStartOf: Int = -1
116 | var topToTopOf: Int = -1
117 | var topToBottomOf: Int = -1
118 | var bottomToTopOf: Int = -1
119 | var bottomToBottomOf: Int = -1
120 | var centerHorizontalOf: Int = -1
121 | var centerVerticalOf: Int = -1
122 | }
123 |
124 |
125 | }
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/views/ProgressBar.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.views
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.*
6 | import android.util.AttributeSet
7 | import android.view.View
8 |
9 | /**
10 | * a linear progress bar
11 | */
12 | class ProgressBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
13 | View(context, attrs, defStyleAttr) {
14 | var progressBackgroundColor: Int = Color.parseColor("#00ff00")
15 | set(value) {
16 | field = value
17 | backgroundPaint.color = value
18 | }
19 | var progressForegroundColor: Int = Color.parseColor("#ff00ff")
20 | set(value) {
21 | field = value
22 | foreGroundPaint.color = value
23 | }
24 |
25 | var colors = intArrayOf()
26 |
27 | var backgroundPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
28 | color = progressBackgroundColor
29 | style = Paint.Style.FILL
30 | }
31 |
32 | var foreGroundPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
33 | color = progressForegroundColor
34 | style = Paint.Style.FILL
35 | }
36 |
37 | var foregroundRectF = RectF()
38 | var backgroundRectF = RectF()
39 |
40 | var rx: Float = 0f
41 | var ry: Float = 0f
42 |
43 | var progress: Float = 0f
44 |
45 | @SuppressLint("DrawAllocation")
46 | override fun onDraw(canvas: Canvas?) {
47 | foreGroundPaint.shader =
48 | if (colors.isEmpty()) null else LinearGradient(0f, 0f, width * progress, height.toFloat(), colors, null, Shader.TileMode.CLAMP)
49 | backgroundRectF.set(0f, 0f, width.toFloat(), height.toFloat())
50 | canvas?.drawRoundRect(backgroundRectF, rx, ry, backgroundPaint)
51 | foregroundRectF.set(0f, 0f, width * progress, height.toFloat())
52 | canvas?.drawRoundRect(foregroundRectF, rx, ry, foreGroundPaint)
53 | }
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/views/Selector.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.views
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 |
9 | open class Selector @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
10 | FrameLayout(context, attrs, defStyleAttr) {
11 | /**
12 | * the unique identifier for a [Selector]
13 | */
14 | var tag: String = "default tag"
15 |
16 | /**
17 | * the identifier for the [SelectorGroup] this [Selector] belongs to
18 | */
19 | var groupTag: String = "default group tag"
20 |
21 | /**
22 | * the [SelectorGroup] this [Selector] belongs to
23 | */
24 | var group: SelectorGroup? = null
25 |
26 | /**
27 | * the layout view for this [Selector]
28 | */
29 | var contentView: View? = null
30 | set(value) {
31 | field = value
32 | value?.let {
33 | addView(it, LayoutParams(MATCH_PARENT, MATCH_PARENT))
34 | setOnClickListener {
35 | group?.onSelectorClick(this)
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * it will be invoked when the selection state of this [Selector] has changed,
42 | * override it if you want customized effect of selected or unselected
43 | */
44 | var onStateChange: ((Selector, Boolean) -> Unit)? = null
45 |
46 | init {
47 | contentView?.let {
48 | addView(it, LayoutParams(MATCH_PARENT, MATCH_PARENT))
49 | setOnClickListener {
50 | group?.onSelectorClick(this)
51 | }
52 | }
53 | }
54 |
55 | override fun setSelected(selected: Boolean) {
56 | val isPreSelected = isSelected
57 | super.setSelected(selected)
58 | if (isPreSelected != selected) {
59 | onStateChange?.invoke(this, selected)
60 | }
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/java/taylor/com/views/SelectorGroup.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.views
2 |
3 |
4 | class SelectorGroup {
5 |
6 | companion object {
7 | /**
8 | * single choice mode, previous [Selector] will be unselected if a new one is selected
9 | */
10 | var MODE_SINGLE = { selectorGroup: SelectorGroup, selector: Selector ->
11 | selectorGroup.run {
12 | find(selector.groupTag)?.let { setSelected(it, false) }
13 | setSelected(selector, true)
14 | }
15 | }
16 |
17 | /**
18 | * multiple choice mode, several [Selector] could be selected in one [SelectorGroup]
19 | */
20 | var MODE_MULTIPLE = { selectorGroup: SelectorGroup, selector: Selector ->
21 | selectorGroup.setSelected(selector, !selector.isSelected)
22 | }
23 | }
24 |
25 | /**
26 | * the selected [Selector]s in this [SelectorGroup]
27 | */
28 | private var selectors = mutableListOf()
29 |
30 | /**
31 | * the choice mode of this [SelectorGroup], there are two default choice mode, which is [singleMode] and [multipleMode]
32 | */
33 | var choiceMode: ((SelectorGroup, Selector) -> Unit)? = null
34 |
35 |
36 | var selectChangeListener: ((List, Boolean) -> Unit)? = null
37 |
38 | fun onSelectorClick(selector: Selector) {
39 | choiceMode?.invoke(this, selector)
40 | }
41 |
42 | fun find(groupTag: String) = selectors.find { it.groupTag == groupTag }
43 |
44 | fun setSelected(selector: Selector, select: Boolean) {
45 | if (select) selectors.add(selector) else selectors.remove(selector)
46 | selector.isSelected = select
47 | if (select) {
48 | selectChangeListener?.invoke(selectors, select)
49 | } else {
50 | selectChangeListener?.invoke(listOf(selector), select)
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_orange_btn.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/diamond_tag.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/drawable/diamond_tag.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_back_black.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/drawable/ic_back_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/ic_member_more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/drawable/ic_member_more.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/tag_checked_shape.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/user_portrait_gender_female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/drawable/user_portrait_gender_female.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/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/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Layout_dsl
3 | Settings
4 |
5 | First Fragment
6 | Second Fragment
7 | Next
8 | Previous
9 |
10 | Hello first fragment
11 | Hello second fragment. Arg: %1$s
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/test/java/taylor/com/ui/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package taylor.com.ui
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/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.4.0'
5 | repositories {
6 | google()
7 | jcenter()
8 |
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:4.1.3'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
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 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## For more details on how to configure your build environment visit
2 | # http://www.gradle.org/docs/current/userguide/build_environment.html
3 | #
4 | # Specifies the JVM arguments used for the daemon process.
5 | # The setting is particularly useful for tweaking memory settings.
6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
8 | #
9 | # When configured, Gradle will run in incubating parallel mode.
10 | # This option should only be used with decoupled projects. More details, visit
11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
12 | # org.gradle.parallel=true
13 | #Thu Oct 01 21:27:15 CST 2020
14 | android.enableJetifier=true
15 | android.useAndroidX=true
16 | kotlin.code.style=official
17 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wisdomtl/Layout_DSL/8e48f4a011e91ad475a3889b2000ffbc9b4d8232/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue May 11 14:52:11 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name='Layout_dsl'
2 | include ':app'
3 |
--------------------------------------------------------------------------------