├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── randomlytyping │ │ └── ccl │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── fonts │ │ │ └── LeagueGothicRegular.otf │ ├── java │ │ ├── com │ │ │ └── randomlytyping │ │ │ │ └── ccl │ │ │ │ ├── AlignmentActivity.kt │ │ │ │ ├── CCLApplication.kt │ │ │ │ ├── ChainsActivity.kt │ │ │ │ ├── CircularConstraintActivity.kt │ │ │ │ ├── ConstraintSetActivity.kt │ │ │ │ ├── DimensionRatioActivity.kt │ │ │ │ ├── DynamicConstraintLayoutActivity.kt │ │ │ │ ├── ListItemActivity.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── util │ │ │ │ └── ActivityExtensions.kt │ │ │ │ └── view │ │ │ │ └── SquareImageView.kt │ │ └── rt │ │ │ └── randamu │ │ │ ├── ConstraintSetExtensions.kt │ │ │ ├── ResourcesExtensions.kt │ │ │ └── ViewDrawableExtensions.kt │ └── res │ │ ├── drawable-xxhdpi │ │ ├── lina_dragon_slave.png │ │ ├── lina_fiery_soul.png │ │ ├── lina_laguna_blade.png │ │ ├── lina_light_strike_array.png │ │ ├── lina_mcbeamish.jpg │ │ ├── overviewicon_agi.png │ │ ├── overviewicon_int.png │ │ └── overviewicon_str.png │ │ ├── drawable-xxxhdpi │ │ ├── unsplash_caspar_rubin_macbook_sql.jpg │ │ ├── unsplash_cory_seward_eggs.jpg │ │ ├── unsplash_danielle_macinnes_artichokes.jpg │ │ ├── unsplash_julia_d_alkmin_ice_tea.jpg │ │ ├── unsplash_maria_fernanda_gonzalez_coffee.jpg │ │ ├── unsplash_michal_grosicki_glitter.jpg │ │ ├── unsplash_mike_kenneally_green_beans.jpg │ │ ├── unsplash_neven_krcmarek_grapes.jpg │ │ ├── unsplash_nousnou_iwasaki_coffee_beans.jpg │ │ ├── unsplash_oreo_cream.jpg │ │ ├── unsplash_roberta_sorge_honey.jpg │ │ ├── unsplash_roberta_sorge_pineapple.jpg │ │ ├── unsplash_roberta_sorge_pomegranate.jpg │ │ ├── unsplash_shumilov_ludmila_pears.jpg │ │ ├── unsplash_tamarcus_brown_ruler.jpg │ │ └── unsplash_tatiana_lapina_macaroon.jpg │ │ ├── drawable │ │ ├── ic_android_black_24dp.xml │ │ ├── ic_arrow_back_black_24dp.xml │ │ ├── ic_arrow_downward_black_24dp.xml │ │ ├── ic_arrow_forward_black_24dp.xml │ │ ├── ic_arrow_upward_black_24dp.xml │ │ ├── ic_circle_black_24dp.xml │ │ ├── ic_dot_black_24dp.xml │ │ ├── ic_favorite_black_24dp.xml │ │ ├── ic_fire_black_24dp.xml │ │ ├── ic_format_align_left_black_24dp.xml │ │ ├── ic_image_aspect_ratio_black_24dp.xml │ │ ├── ic_link_black_24dp.xml │ │ ├── ic_list_black_24dp.xml │ │ ├── ic_snowflake_black_24dp.xml │ │ ├── ic_spa_black_24dp.xml │ │ ├── ic_stop_black_24dp.xml │ │ ├── ic_swap_horiz_black_24dp.xml │ │ ├── ic_view_list_black_24dp.xml │ │ ├── ic_widgets_black_24dp.xml │ │ └── rect_stroke_grey.xml │ │ ├── layout │ │ ├── activity_container_linear_layout.xml │ │ ├── activity_container_scroll_view.xml │ │ ├── activity_main.xml │ │ ├── content_alignment.xml │ │ ├── content_chains.xml │ │ ├── content_circular_constraint.xml │ │ ├── content_constraintset_01.xml │ │ ├── content_constraintset_02.xml │ │ ├── content_constraintset_02_buggy.xml │ │ ├── content_dimension_ratio.xml │ │ ├── content_dynamic_constraint_layout.xml │ │ ├── content_recycler_view.xml │ │ ├── layout_constraints_basic.xml │ │ ├── layout_dynamic_image.xml │ │ ├── layout_dynamic_text.xml │ │ ├── layout_guidelines.xml │ │ ├── list_item_example.xml │ │ ├── list_item_unsplash.xml │ │ └── list_item_unsplash_expanded.xml │ │ ├── menu │ │ └── constraint_set.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-land │ │ └── dimens.xml │ │ ├── values-sw600dp │ │ └── dimens.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── examples.xml │ │ ├── ids.xml │ │ ├── items.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── randomlytyping │ └── ccl │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | ## Cool ConstraintLayout 2 | Examples from my talk "Cool ConstraintLayout" which gives examples of interesting things to do with `ConstraintLayout` using its unique features. 3 | 4 | ## Examples 5 | 6 | ### Dimension Ratio 7 | 8 | ### Alignment 9 | 10 | ### Chains 11 | 12 | ### ConstraintSet 13 | 14 | ### ConstraintLayout List Item 15 | 16 | ### Dynamic ConstraintLayout 17 | -------------------------------------------------------------------------------- /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 | ext.versions = [ 6 | // Android 7 | targetSdkVersion : 26, 8 | minSdkVersion : 21, 9 | buildtools : "26.0.2", 10 | support : "27.0.0", 11 | 12 | // Components 13 | constraintlayout : "1.1.0-beta3", 14 | 15 | // Logging 16 | timber : "4.5.1", 17 | 18 | // UI 19 | calligraphy : "2.3.0", 20 | 21 | // Utility 22 | butterknife : "8.8.1", 23 | kotterknife : "0.1.0-SNAPSHOT" 24 | ] 25 | 26 | def versionMajor = 0 27 | def versionMinor = 1 28 | def versionPatch = 0 29 | def versionBuild = 0 30 | 31 | 32 | android { 33 | compileSdkVersion versions.targetSdkVersion 34 | buildToolsVersion versions.buildtools 35 | defaultConfig { 36 | applicationId "com.randomlytyping.ccl" 37 | minSdkVersion versions.minSdkVersion 38 | targetSdkVersion versions.targetSdkVersion 39 | versionCode versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild 40 | versionName "${versionMajor}.${versionMinor}.${versionPatch}" 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | 43 | vectorDrawables.useSupportLibrary = true 44 | } 45 | buildTypes { 46 | release { 47 | minifyEnabled false 48 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 49 | } 50 | } 51 | } 52 | 53 | dependencies { 54 | implementation fileTree(dir: 'libs', include: ['*.jar']) 55 | 56 | // Android 57 | implementation "com.android.support:support-v4:$versions.support" 58 | implementation "com.android.support:appcompat-v7:$versions.support" 59 | implementation "com.android.support:design:$versions.support" 60 | implementation "com.android.support:support-annotations:$versions.support" 61 | 62 | // Components 63 | implementation "com.android.support.constraint:constraint-layout:$versions.constraintlayout" 64 | implementation "com.android.support:cardview-v7:$versions.support" 65 | 66 | // Kotlin 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 68 | 69 | // Logging 70 | implementation "com.jakewharton.timber:timber:$versions.timber" 71 | 72 | // UI 73 | implementation "uk.co.chrisjenx:calligraphy:$versions.calligraphy" 74 | 75 | // Utility 76 | implementation "com.jakewharton:kotterknife:$versions.kotterknife" 77 | 78 | // Testing 79 | androidTestImplementation ('com.android.support.test.espresso:espresso-core:2.2.2', { 80 | exclude group: 'com.android.support', module: 'support-annotations' 81 | }) 82 | testImplementation 'junit:junit:4.12' 83 | } 84 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/huyen/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/randomlytyping/ccl/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.randomlytyping.ccl", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 31 | 35 | 39 | 43 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/assets/fonts/LeagueGothicRegular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/assets/fonts/LeagueGothicRegular.otf -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/AlignmentActivity.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl 2 | 3 | import android.os.Bundle 4 | import android.support.annotation.IdRes 5 | import android.support.constraint.ConstraintLayout 6 | import android.support.v4.content.ContextCompat 7 | import android.support.v7.app.AppCompatActivity 8 | import android.transition.TransitionManager 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import android.widget.RadioButton 13 | import android.widget.RadioGroup 14 | import android.widget.SeekBar 15 | import com.randomlytyping.ccl.util.setUpAppBar 16 | import kotterknife.bindView 17 | import kotterknife.bindViews 18 | import rt.randamu.tintBackground 19 | import rt.randamu.toConstraintSet 20 | import rt.randamu.update 21 | 22 | /** 23 | * Examples of ways to leverage edge constraints to do interesting/useful component alignment. 24 | */ 25 | class AlignmentActivity : AppCompatActivity() { 26 | 27 | //region // Properties 28 | private val anchorViewIds = 29 | arrayOf(R.id.anchor_top, R.id.anchor_bottom, R.id.anchor_start, R.id.anchor_end) 30 | 31 | // Views 32 | private val constraintLayout by bindView(R.id.constraint_layout) 33 | private val ratioGroup by bindView(R.id.ratio_group) 34 | private val seekBar by bindView(R.id.seek_bar) 35 | private val allAnchors by bindViews( 36 | R.id.anchor_top, R.id.anchor_bottom, 37 | R.id.anchor_start, R.id.anchor_end, 38 | R.id.manual_anchor_top, R.id.manual_anchor_bottom, 39 | R.id.manual_anchor_start, R.id.manual_anchor_end 40 | ) 41 | private val manualAnchorTop by bindView(R.id.manual_anchor_top) 42 | private val manualAnchorBottom by bindView(R.id.manual_anchor_bottom) 43 | private val manualAnchorStart by bindView(R.id.manual_anchor_start) 44 | private val manualAnchorEnd by bindView(R.id.manual_anchor_end) 45 | 46 | // Resources 47 | private val anchorMinSize by lazy { resources.getDimensionPixelSize(R.dimen.alignment_anchor_min_size) } 48 | private val anchorMarginHorizontal by lazy { resources.getDimensionPixelSize(R.dimen.content_margin_horizontal) } 49 | private val anchorMarginVertical by lazy { resources.getDimensionPixelSize(R.dimen.alignment_rectangle_margin_vertical) } 50 | 51 | // Constraints 52 | 53 | // A ConstraintSet that can be updated and applied to the current layout. 54 | private val constraintSet by lazy { constraintLayout.toConstraintSet() } 55 | 56 | //endregion 57 | 58 | //region // Activity lifecycle 59 | 60 | override fun onCreate(savedInstanceState: Bundle?) { 61 | super.onCreate(savedInstanceState) 62 | setContentView(R.layout.activity_container_scroll_view) 63 | 64 | // Inflate content and bind views. 65 | LayoutInflater.from(this@AlignmentActivity).inflate(R.layout.content_alignment, findViewById(R.id.scroll_view)) 66 | 67 | setUpAppBar() 68 | 69 | // Set up anchor views. 70 | for (i in 0 until allAnchors.size) { 71 | allAnchors[i].tintBackground(ContextCompat.getColor(this, R.color.colorAccent)) 72 | } 73 | updateManualAnchors() 74 | 75 | // Set up seek bar to scale up/down size of the anchors. 76 | seekBar.run { 77 | max = resources.getDimensionPixelSize(R.dimen.alignment_anchor_max_size) - anchorMinSize 78 | setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 79 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { 80 | updateConstraintAnchors() 81 | updateManualAnchors() 82 | } 83 | 84 | override fun onStartTrackingTouch(seekBar: SeekBar) = Unit 85 | override fun onStopTrackingTouch(seekBar: SeekBar) = Unit 86 | }) 87 | } 88 | 89 | // Set up ratio radio group and add listener to change dimension ratio of hero image 90 | // when clicked. 91 | ratioGroup.run { 92 | // Set the label text for each RadioButton. 93 | for (i in 0 until childCount) { 94 | (getChildAt(i) as? RadioButton)?.run { text = getDimensionRatio(id) } 95 | } 96 | 97 | // Apply ConstraintSet for each ratio as the radio buttons are checked. 98 | setOnCheckedChangeListener { _, _ -> updateConstraintAnchors(true) } 99 | 100 | // Initialize ConstraintSet. 101 | check(R.id.ratio_01) 102 | } 103 | } 104 | 105 | //endregion 106 | 107 | //region // ConstraintSet updating 108 | 109 | /** 110 | * Updates the size of constraint-positioned anchors. 111 | */ 112 | private fun updateConstraintAnchors(transition: Boolean = false) { 113 | if (transition) { 114 | TransitionManager.beginDelayedTransition(constraintLayout) 115 | } 116 | 117 | // Calculate anchor size based on seek bar position. 118 | val anchorSize = anchorMinSize + seekBar.progress 119 | 120 | // Update ConstraintSet and apply to layout. 121 | constraintSet.update(constraintLayout) { 122 | setDimensionRatio(R.id.hero, getDimensionRatio(ratioGroup.checkedRadioButtonId)) 123 | for (i in 0 until anchorViewIds.size) { 124 | constrainWidth(anchorViewIds[i], anchorSize) 125 | constrainHeight(anchorViewIds[i], anchorSize) 126 | } 127 | } 128 | } 129 | 130 | /** 131 | * Translate the view ID of a ratio radio button to its corresponding label string. 132 | */ 133 | private fun getDimensionRatio(@IdRes id: Int) = getString(when (id) { 134 | R.id.ratio_01 -> R.string.ratio_3_1 135 | R.id.ratio_02 -> R.string.ratio_16_9 136 | R.id.ratio_03 -> R.string.ratio_4_3 137 | else -> R.string.ratio_3_1 138 | }) 139 | 140 | //endregion 141 | 142 | //region // Manual anchor updates 143 | 144 | /** 145 | * Update the sizes and margins of all the manually-positioned anchors via layout parameters. 146 | */ 147 | private fun updateManualAnchors() { 148 | val size = anchorMinSize + seekBar.progress 149 | val halfSize = Math.round(size * 0.5f) 150 | val horizontalMargin = anchorMarginHorizontal - halfSize 151 | val verticalMargin = anchorMarginVertical - halfSize 152 | updateManualAnchor(manualAnchorTop, size, top = verticalMargin) 153 | updateManualAnchor(manualAnchorBottom, size, bottom = verticalMargin) 154 | updateManualAnchor(manualAnchorStart, size, start = horizontalMargin) 155 | updateManualAnchor(manualAnchorEnd, size, end = horizontalMargin) 156 | } 157 | 158 | /** 159 | * Sets the size and margins for a single manually-positioned anchor. 160 | */ 161 | private fun updateManualAnchor( 162 | anchor: View, 163 | size: Int, 164 | top: Int = 0, 165 | bottom: Int = 0, 166 | start: Int = 0, 167 | end: Int = 0 168 | ) { 169 | anchor.layoutParams = (anchor.layoutParams as ViewGroup.MarginLayoutParams).apply { 170 | width = size 171 | height = size 172 | topMargin = top 173 | bottomMargin = bottom 174 | marginStart = start 175 | marginEnd = end 176 | } 177 | } 178 | 179 | // endregion 180 | } -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/CCLApplication.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl 2 | 3 | import android.app.Application 4 | import timber.log.Timber 5 | 6 | /** 7 | * Custom application class for bootstrapping/setup. 8 | */ 9 | class CCLApplication : Application() { 10 | override fun onCreate() { 11 | super.onCreate() 12 | 13 | Timber.plant(Timber.DebugTree()) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/ChainsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl 2 | 3 | import android.os.Bundle 4 | import android.support.constraint.ConstraintLayout 5 | import android.support.constraint.ConstraintLayout.LayoutParams.* 6 | import android.support.v7.app.AppCompatActivity 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import com.randomlytyping.ccl.util.setUpAppBar 10 | import kotterknife.bindView 11 | import rt.randamu.toConstraintSet 12 | import rt.randamu.update 13 | 14 | /** 15 | * Examples of chains including interwoven and chained chains. 16 | */ 17 | class ChainsActivity : AppCompatActivity() { 18 | 19 | //region // Properties 20 | 21 | private val constraintLayout by bindView(R.id.constraint_layout) 22 | private val constraintSet by lazy { constraintLayout.toConstraintSet() } 23 | 24 | private var indexVertical = 0 25 | private var indexD = 0 26 | 27 | //endregion 28 | 29 | //region // Activity lifecycle 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | setContentView(R.layout.activity_container_linear_layout) 34 | 35 | // Inflate content and bind views. 36 | LayoutInflater.from(this).inflate(R.layout.content_chains, findViewById(R.id.linear_layout)) 37 | 38 | setUpAppBar() 39 | 40 | findViewById(R.id.toggle_vertical).setOnClickListener { 41 | indexVertical = (indexVertical + 1) % CHAIN_STYLE.size 42 | constraintSet.update(constraintLayout) { 43 | setVerticalChainStyle(R.id.text_01, CHAIN_STYLE[indexVertical]) 44 | } 45 | } 46 | 47 | findViewById(R.id.toggle_d).setOnClickListener { 48 | indexD = (indexD + 1) % CHAIN_STYLE.size 49 | constraintSet.update(constraintLayout) { 50 | setHorizontalChainStyle(R.id.text_04, indexD) 51 | } 52 | } 53 | } 54 | 55 | //endregion 56 | 57 | companion object { 58 | /** 59 | * List of chain styles through which to cycle. 60 | */ 61 | private val CHAIN_STYLE = listOf(CHAIN_SPREAD, CHAIN_SPREAD_INSIDE, CHAIN_PACKED) 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/CircularConstraintActivity.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import android.view.LayoutInflater 7 | import com.randomlytyping.ccl.util.setUpAppBar 8 | import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper 9 | 10 | /** 11 | * Example of circular constraints. 12 | */ 13 | class CircularConstraintActivity : AppCompatActivity() { 14 | 15 | //region // Activity lifecycle 16 | 17 | override fun attachBaseContext(newBase: Context?) { 18 | super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)) 19 | } 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_container_linear_layout) 24 | 25 | // Inflate content and bind views. 26 | LayoutInflater.from(this) 27 | .inflate(R.layout.content_circular_constraint, findViewById(R.id.linear_layout)) 28 | 29 | setUpAppBar() 30 | } 31 | 32 | //endregion 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/ConstraintSetActivity.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.os.Bundle 6 | import android.support.constraint.ConstraintLayout 7 | import android.support.transition.TransitionManager 8 | import android.support.v4.graphics.drawable.DrawableCompat 9 | import android.support.v7.app.AppCompatActivity 10 | import android.view.LayoutInflater 11 | import android.view.Menu 12 | import android.view.MenuItem 13 | import com.randomlytyping.ccl.util.setUpAppBar 14 | import kotterknife.bindView 15 | import rt.randamu.ConstraintSets 16 | import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper 17 | 18 | /** 19 | * Examples of ConstraintSet including transitioning between sets. 20 | */ 21 | class ConstraintSetActivity : AppCompatActivity() { 22 | 23 | //region Properties 24 | 25 | private val constraintLayout by bindView(R.id.constraint_layout) 26 | private val constraintSet01 by lazy { ConstraintSets.from(this, R.layout.content_constraintset_01) } 27 | private val constraintSet02 by lazy { ConstraintSets.from(this, R.layout.content_constraintset_02) } 28 | 29 | private var original = true 30 | 31 | //endregion 32 | 33 | //region // Activity lifecycle 34 | 35 | override fun attachBaseContext(newBase: Context?) { 36 | super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)) 37 | } 38 | 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | setContentView(R.layout.activity_container_linear_layout) 42 | 43 | // Inflate content and bind views. 44 | LayoutInflater.from(this).inflate(R.layout.content_constraintset_01, findViewById(R.id.linear_layout)) 45 | 46 | setUpAppBar() 47 | } 48 | 49 | // endregion 50 | 51 | //region // Action item handling 52 | 53 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 54 | menuInflater.inflate(R.menu.constraint_set, menu) 55 | DrawableCompat.setTint(menu.findItem(R.id.action_swap_constraint_set).icon, Color.WHITE) 56 | return true 57 | } 58 | 59 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 60 | if (item.itemId == R.id.action_swap_constraint_set) { 61 | TransitionManager.beginDelayedTransition(constraintLayout) 62 | if (original) constraintSet02.applyTo(constraintLayout) 63 | else constraintSet01.applyTo(constraintLayout) 64 | original = !original 65 | return true 66 | } else { 67 | return super.onOptionsItemSelected(item) 68 | } 69 | 70 | // endregion 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/DimensionRatioActivity.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl 2 | 3 | import android.os.Bundle 4 | import android.support.constraint.ConstraintLayout 5 | import android.support.v7.app.AppCompatActivity 6 | import android.view.LayoutInflater 7 | import com.randomlytyping.ccl.util.setUpAppBar 8 | 9 | /** 10 | * Examples of using [ConstraintLayout]'s dimension ratio. 11 | */ 12 | class DimensionRatioActivity : AppCompatActivity() { 13 | 14 | //region // Activity lifecycle 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.activity_container_linear_layout) 19 | 20 | // Inflate content and bind views. 21 | LayoutInflater.from(this).inflate(R.layout.content_dimension_ratio, findViewById(R.id.linear_layout)) 22 | 23 | setUpAppBar() 24 | } 25 | 26 | //endregion 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/DynamicConstraintLayoutActivity.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.annotation.IdRes 6 | import android.support.constraint.ConstraintLayout 7 | import android.support.constraint.ConstraintSet 8 | import android.support.constraint.ConstraintSet.* 9 | import android.support.v7.app.AppCompatActivity 10 | import android.view.LayoutInflater 11 | import android.view.View 12 | import android.view.View.NO_ID 13 | import android.widget.ImageView 14 | import android.widget.TextView 15 | import com.randomlytyping.ccl.util.setUpAppBar 16 | import kotterknife.bindView 17 | import rt.randamu.getResourceIdArray 18 | import rt.randamu.toConstraintSet 19 | import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper 20 | import java.util.Random 21 | 22 | /** 23 | * Demonstration of dynamically building up a layout using [ConstraintSet]. 24 | * 25 | * Note: the code here is intentionally more verbose as I tend to use this 26 | * example for presentations where I want to explain in more detail. 27 | */ 28 | class DynamicConstraintLayoutActivity : AppCompatActivity() { 29 | 30 | // Layout generation 31 | private val inflater by lazy { LayoutInflater.from(this) } 32 | private val constraintLayout by bindView(R.id.constraint_layout) 33 | private val constraintSet by lazy { constraintLayout.toConstraintSet() } 34 | 35 | // Dimension resources 36 | private val rowHeight by lazy { resources.getDimensionPixelSize(R.dimen.dynamic_row_height) } 37 | private val elevation by lazy { resources.getDimension(R.dimen.elevation_content) } 38 | 39 | // Random images/text to choose 40 | private val images by lazy { resources.getResourceIdArray(R.array.unsplash_images) } 41 | private val strings by lazy { resources.getStringArray(R.array.random_words) } 42 | 43 | // Bookkeeping 44 | @IdRes private var lastViewId: Int = NO_ID 45 | @IdRes private var lastRowId: Int = PARENT_ID 46 | 47 | private var indexImages = 0 48 | private var indexStrings = 0 49 | private var currentRowSize = 0 50 | 51 | // Coin flip 52 | private val randomNumber = Random(1) 53 | 54 | //region // Activity lifecycle 55 | 56 | override fun onCreate(savedInstanceState: Bundle?) { 57 | super.onCreate(savedInstanceState) 58 | setContentView(R.layout.activity_container_linear_layout) 59 | 60 | // Inflate content and bind views. 61 | LayoutInflater.from(this).inflate(R.layout.content_dynamic_constraint_layout, findViewById(R.id.linear_layout)) 62 | 63 | setUpAppBar() 64 | 65 | findViewById(R.id.button_add_image).setOnClickListener { addItem(this::newImage) } 66 | findViewById(R.id.button_add_text).setOnClickListener { addItem(this::newText) } 67 | findViewById(R.id.button_clear).setOnClickListener { 68 | constraintLayout.removeAllViews() 69 | lastRowId = PARENT_ID 70 | lastViewId = NO_ID 71 | currentRowSize = 0 72 | constraintSet.clone(constraintLayout) 73 | } 74 | } 75 | 76 | //endregion 77 | 78 | //region // Calligraphy bootstrapping 79 | 80 | override fun attachBaseContext(newBase: Context) { 81 | super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)) 82 | } 83 | 84 | //endregion 85 | 86 | //region // Dynamic ConstraintLayout building 87 | 88 | /** 89 | * Add item as either an item in the current row or as the single item in a whole new row. 90 | */ 91 | private fun addItem(newItem: () -> View) { 92 | if (currentRowSize > 0 && flip()) addRowItem(newItem()) else addNewRow(newItem()) 93 | } 94 | 95 | /** 96 | * Add given [view] as whole new row in layout. 97 | */ 98 | private fun addNewRow(view: View) { 99 | constraintLayout.addView(view) 100 | 101 | if (currentRowSize > 0) { 102 | lastRowId = lastViewId 103 | } 104 | 105 | val id = view.id 106 | constraintSet.constrainWidth(id, MATCH_CONSTRAINT) 107 | constraintSet.constrainHeight(id, rowHeight) 108 | constraintSet.connect(id, TOP, lastRowId, if (lastRowId == PARENT_ID) TOP else BOTTOM) 109 | constraintSet.connect(id, START, PARENT_ID, START) 110 | constraintSet.connect(id, END, PARENT_ID, END) 111 | 112 | // Toggle elevation to elevate last added view. 113 | constraintSet.setElevation(id, elevation) 114 | constraintSet.setElevation(lastViewId, 0f) 115 | 116 | // Apply constraints. 117 | constraintSet.applyTo(constraintLayout) 118 | 119 | // Update current row count. 120 | currentRowSize = 1 121 | 122 | // Record as last view added. 123 | lastViewId = id 124 | } 125 | 126 | /** 127 | * Add given [view] as constituent item of current row in layout. 128 | */ 129 | private fun addRowItem(view: View) { 130 | if (currentRowSize == 0) { 131 | addNewRow(view) 132 | return 133 | } 134 | 135 | constraintLayout.addView(view) 136 | 137 | val id = view.id 138 | 139 | // Initialize width and height of new view. 140 | constraintSet.constrainWidth(id, MATCH_CONSTRAINT) 141 | constraintSet.constrainHeight(id, rowHeight) 142 | 143 | // Constrain new view vertically. 144 | constraintSet.connect(id, TOP, lastRowId, if (lastRowId == PARENT_ID) TOP else BOTTOM) 145 | 146 | // Update current row. 147 | currentRowSize++ 148 | 149 | if (currentRowSize == 2) { 150 | // Once the row has two items in it, create a horizontal chain. 151 | constraintSet.createHorizontalChainRtl( 152 | PARENT_ID, START, 153 | PARENT_ID, END, 154 | intArrayOf(lastViewId, id), floatArrayOf(1f, 1f), 155 | CHAIN_SPREAD_INSIDE) 156 | } else { 157 | // The row already has at least two items so just add the new view to the chain. 158 | constraintSet.addToHorizontalChainRTL(id, lastViewId, PARENT_ID) 159 | constraintSet.setHorizontalWeight(id, 1f) 160 | } 161 | 162 | // Fix for bug in `setHorizontalWeight`. 163 | constraintSet.setHorizontalWeight(lastViewId, 1f) 164 | 165 | // Toggle elevation to elevate last added view. 166 | constraintSet.setElevation(id, elevation) 167 | constraintSet.setElevation(lastViewId, 0f) 168 | 169 | // Apply constraints. 170 | constraintSet.applyTo(constraintLayout) 171 | 172 | // Record as last view added. 173 | lastViewId = id 174 | } 175 | 176 | /** 177 | * Create new ImageView and set its text to the next image drawable in the list. 178 | */ 179 | private fun newImage(): ImageView { 180 | val imageView = 181 | inflater.inflate(R.layout.layout_dynamic_image, constraintLayout, false) as ImageView 182 | return imageView.apply { 183 | id = View.generateViewId() 184 | setImageResource(images[indexImages]) 185 | 186 | // Adjust index. 187 | indexImages = (indexImages + 1) % images.size 188 | } 189 | } 190 | 191 | /** 192 | * Create new TextView and set its text to the next string in the list. 193 | */ 194 | private fun newText(): TextView { 195 | val textView = 196 | inflater.inflate(R.layout.layout_dynamic_text, constraintLayout, false) as TextView 197 | return textView.apply { 198 | id = View.generateViewId() 199 | text = strings[indexStrings] 200 | 201 | // Adjust index 202 | indexStrings = (indexStrings + 1) % strings.size 203 | } 204 | } 205 | 206 | //endregion 207 | 208 | //region // Randomizer 209 | 210 | /** 211 | * Flip a coin. Used to decide between "new row" or "add to current row". 212 | */ 213 | private fun flip() = randomNumber.nextInt(2) == 0 214 | 215 | //endregion 216 | } 217 | -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/ListItemActivity.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.graphics.drawable.ColorDrawable 6 | import android.graphics.drawable.Drawable 7 | import android.os.Bundle 8 | import android.support.annotation.LayoutRes 9 | import android.support.constraint.ConstraintLayout 10 | import android.support.constraint.ConstraintSet 11 | import android.support.v4.content.ContextCompat 12 | import android.support.v7.app.AppCompatActivity 13 | import android.support.v7.widget.LinearLayoutManager 14 | import android.support.v7.widget.RecyclerView 15 | import android.transition.TransitionManager 16 | import android.view.LayoutInflater 17 | import android.view.View 18 | import android.view.ViewGroup 19 | import android.widget.ImageView 20 | import android.widget.TextView 21 | import com.randomlytyping.ccl.util.setUpAppBar 22 | import kotterknife.bindView 23 | import rt.randamu.ConstraintSets 24 | import rt.randamu.getResourceIdArray 25 | import rt.randamu.toConstraintSet 26 | 27 | /** 28 | * Example of using [ConstraintLayout] as roots for list items. 29 | */ 30 | class ListItemActivity : AppCompatActivity() { 31 | 32 | //region // Properties 33 | private val recyclerView by bindView(R.id.recycler_view) 34 | 35 | //endregion 36 | 37 | //region // Activity lifecycle 38 | 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | setContentView(R.layout.activity_container_linear_layout) 42 | 43 | // Inflate content and bind views. 44 | LayoutInflater.from(this).inflate(R.layout.content_recycler_view, findViewById(R.id.linear_layout)) 45 | 46 | setUpAppBar() 47 | 48 | recyclerView.run { 49 | layoutManager = LinearLayoutManager(this@ListItemActivity) 50 | adapter = UnsplashAdapter(this@ListItemActivity) 51 | } 52 | } 53 | 54 | //endregion 55 | 56 | //region // RecyclerView/List 57 | 58 | /** 59 | * RecyclerView adapter 60 | * 61 | * @property context The current context. 62 | * @property inflater Layout inflater. 63 | * @property images List of drawable resource IDs for displayed photos. 64 | * @property attributions List of string resource IDs for image attributions. 65 | */ 66 | private class UnsplashAdapter( 67 | private val context: Context, 68 | private val inflater: LayoutInflater = LayoutInflater.from(context) 69 | ) : RecyclerView.Adapter() { 70 | 71 | /** 72 | * List of string resource IDs for image URLs. 73 | */ 74 | private val urls: IntArray = context.resources.getResourceIdArray(R.array.unsplash_urls) 75 | private val images: IntArray = context.resources.getResourceIdArray(R.array.unsplash_images) 76 | private val attributions: IntArray = context.resources.getResourceIdArray(R.array.unsplash_attributions) 77 | 78 | 79 | // 80 | // RecyclerView.Adapter implementation 81 | // 82 | 83 | override fun getItemId(position: Int) = position.toLong() 84 | 85 | override fun getItemCount() = images.size 86 | 87 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 88 | UnsplashViewHolder(inflater.inflate(R.layout.list_item_unsplash, parent, false), 89 | R.layout.list_item_unsplash_expanded) 90 | 91 | override fun onBindViewHolder(holder: UnsplashViewHolder, position: Int) { 92 | holder.imageDrawable = ContextCompat.getDrawable(context, images[position]) 93 | holder.attributionResId = attributions[position] 94 | holder.urlResId = urls[position] 95 | } 96 | } 97 | 98 | /** 99 | * View holder 100 | */ 101 | private class UnsplashViewHolder(itemView: View, 102 | @LayoutRes alternateLayout: Int, 103 | private val constraintLayout: ConstraintLayout = itemView as ConstraintLayout, 104 | private val attributionView: TextView = itemView.findViewById(R.id.attribution), 105 | private val urlView: TextView = itemView.findViewById(R.id.url), 106 | private val imageView: ImageView = itemView.findViewById(R.id.image), 107 | private var primary: Boolean = true) 108 | : RecyclerView.ViewHolder(itemView) { 109 | 110 | 111 | private val constraintSet01: ConstraintSet = constraintLayout.toConstraintSet() 112 | private val constraintSet02: ConstraintSet = ConstraintSets.from(itemView.context, alternateLayout) 113 | 114 | /** 115 | * ID of attribution string resource. 116 | */ 117 | var attributionResId = 0 118 | set(value) { 119 | attributionView.setText(value) 120 | } 121 | 122 | /** 123 | * ID of url string resource. 124 | */ 125 | var urlResId = 0 126 | set(value) { 127 | urlView.setText(value) 128 | } 129 | 130 | /** 131 | * Drawable resource. 132 | */ 133 | var imageDrawable: Drawable? = ColorDrawable(Color.TRANSPARENT) 134 | set(value) { 135 | imageView.setImageDrawable(value) 136 | } 137 | 138 | init { 139 | itemView.setOnClickListener { swap() } 140 | } 141 | 142 | //region // Constraint set switching 143 | 144 | /** 145 | * Swap constraint sets. 146 | */ 147 | fun swap() { 148 | TransitionManager.beginDelayedTransition(constraintLayout) 149 | if (primary) constraintSet02.applyTo(constraintLayout) 150 | else constraintSet01.applyTo(constraintLayout) 151 | primary = !primary 152 | } 153 | } 154 | 155 | //endregion 156 | } 157 | -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.support.annotation.ColorInt 6 | import android.support.annotation.StringRes 7 | import android.support.v4.content.ContextCompat 8 | import android.support.v4.graphics.drawable.DrawableCompat 9 | import android.support.v7.app.AppCompatActivity 10 | import android.support.v7.widget.LinearLayoutManager 11 | import android.support.v7.widget.RecyclerView 12 | import android.view.View 13 | import android.view.ViewGroup 14 | import android.widget.TextView 15 | import rt.randamu.getResourceIdArray 16 | import kotlin.properties.Delegates 17 | 18 | /** 19 | * Launcher activity + example selector. 20 | */ 21 | class MainActivity : AppCompatActivity() { 22 | 23 | //region // Activity lifecycle 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | setContentView(R.layout.activity_main) 28 | 29 | findViewById(android.R.id.list).run { 30 | adapter = ExampleListAdapter() 31 | layoutManager = LinearLayoutManager(this@MainActivity) 32 | } 33 | } 34 | 35 | //endregion 36 | 37 | //region // Example navigation 38 | 39 | /** 40 | * Start activity associated with given example title. 41 | */ 42 | fun navigateToExample(@StringRes exampleResId: Int) { 43 | startActivity(Intent(this, when (exampleResId) { 44 | R.string.example_alignment -> AlignmentActivity::class.java 45 | R.string.example_chains -> ChainsActivity::class.java 46 | R.string.example_circular -> CircularConstraintActivity::class.java 47 | R.string.example_list_item -> ListItemActivity::class.java 48 | R.string.example_constraint_set -> ConstraintSetActivity::class.java 49 | R.string.example_dimension_ratio -> DimensionRatioActivity::class.java 50 | R.string.example_dynamic_constraint_layout -> DynamicConstraintLayoutActivity::class.java 51 | else -> throw IllegalArgumentException("Invalid item: $exampleResId") 52 | })) 53 | } 54 | 55 | //endregion 56 | 57 | //region // Example RecyclerView/List 58 | 59 | /** 60 | * RecyclerView adapter 61 | * 62 | * @property examples List of string resource IDs that represent all the navigable examples. 63 | * @property icons List of icon resource IDs that represent all the navigable examples. 64 | */ 65 | private inner class ExampleListAdapter( 66 | private val examples: IntArray = resources.getResourceIdArray(R.array.examples), 67 | private val icons: IntArray = resources.getResourceIdArray(R.array.example_icons) 68 | ) : RecyclerView.Adapter() { 69 | 70 | /** 71 | * Color used to tint icons. 72 | */ 73 | @ColorInt private val tint: Int = 74 | ContextCompat.getColor(this@MainActivity, R.color.icon_active_color) 75 | 76 | // 77 | // RecyclerView.Adapter implementation 78 | // 79 | 80 | override fun getItemCount() = examples.size 81 | 82 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 83 | ExampleViewHolder( 84 | layoutInflater.inflate(R.layout.list_item_example, parent, false), 85 | this@MainActivity::navigateToExample, 86 | tint 87 | ) 88 | 89 | override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) { 90 | holder.run { 91 | resId = examples[position] 92 | iconId = icons[position] 93 | } 94 | } 95 | } 96 | 97 | //endregion 98 | 99 | //region // Inner classes 100 | 101 | /** 102 | * View holder 103 | * 104 | * @param itemView View representing item 105 | * @param exampleListener Action executed when example item is selected. 106 | * @property tint Tint applied to example icon. 107 | */ 108 | private class ExampleViewHolder( 109 | itemView: View, 110 | exampleListener: (Int) -> Unit, 111 | @ColorInt private val tint: Int 112 | ) : RecyclerView.ViewHolder(itemView) { 113 | 114 | /** 115 | * Item text view. 116 | */ 117 | private val textView: TextView = itemView as TextView 118 | 119 | /** 120 | * ID of string resource for example title. 121 | */ 122 | var resId by Delegates.observable(0) { _, _, new -> 123 | textView.setText(new) 124 | } 125 | 126 | /** 127 | * ID of drawable resource for example icon. 128 | */ 129 | var iconId by Delegates.observable(0) { _, _, new -> 130 | textView.setCompoundDrawablesRelativeWithIntrinsicBounds(new, 0, 0, 0) 131 | DrawableCompat.setTint(textView.compoundDrawablesRelative[0], tint) 132 | } 133 | 134 | init { 135 | itemView.setOnClickListener { exampleListener(resId) } 136 | } 137 | } 138 | 139 | //endregion 140 | } -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/util/ActivityExtensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("LayoutUtils") 2 | 3 | package com.randomlytyping.ccl.util 4 | 5 | import android.support.v7.app.AppCompatActivity 6 | import com.randomlytyping.ccl.R 7 | 8 | // Utility methods / extensions related to activity layouts 9 | 10 | /** 11 | * Sets up an activity's app bar. 12 | */ 13 | fun AppCompatActivity.setUpAppBar() { 14 | // Set up app bar. 15 | setSupportActionBar(findViewById(R.id.app_bar)) 16 | supportActionBar?.run { 17 | setDisplayShowHomeEnabled(true) 18 | setDisplayHomeAsUpEnabled(true) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/randomlytyping/ccl/view/SquareImageView.kt: -------------------------------------------------------------------------------- 1 | package com.randomlytyping.ccl.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.ImageView 6 | 7 | /** 8 | * Image view that always displays content as a square. 9 | */ 10 | class SquareImageView : ImageView { 11 | 12 | //region // View Constructors 13 | 14 | constructor(context: Context) : super(context) 15 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) 16 | 17 | //endregion 18 | 19 | //region // View Overrides 20 | 21 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 22 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 23 | 24 | val size = Math.min(measuredWidth, measuredHeight) 25 | setMeasuredDimension(size, size) 26 | } 27 | 28 | //endregion 29 | } -------------------------------------------------------------------------------- /app/src/main/java/rt/randamu/ConstraintSetExtensions.kt: -------------------------------------------------------------------------------- 1 | package rt.randamu 2 | 3 | import android.content.Context 4 | import android.support.annotation.LayoutRes 5 | import android.support.constraint.ConstraintLayout 6 | import android.support.constraint.ConstraintSet 7 | 8 | // Utility and extension methods for ConstraintSet 9 | 10 | /** 11 | * Class containing static factory methods for [ConstraintSet] instances. 12 | */ 13 | class ConstraintSets private constructor() { 14 | companion object { 15 | /** 16 | * @return A [ConstraintSet] cloned from layout resource file with given [resId]. 17 | */ 18 | fun from(context: Context, @LayoutRes resId: Int) = 19 | ConstraintSet().apply { clone(context, resId) } 20 | } 21 | } 22 | 23 | /** 24 | * Makes changes to the receiver [ConstraintSet] via the [updates] block and applies the resulting 25 | * new constraints on the given [constraintLayout]. 26 | */ 27 | inline fun ConstraintSet.update( 28 | constraintLayout: ConstraintLayout, 29 | clone: Boolean = false, 30 | updates: ConstraintSet.() -> Unit 31 | ) { 32 | if (clone) { 33 | clone(constraintLayout) 34 | } 35 | updates() 36 | applyTo(constraintLayout) 37 | } 38 | 39 | /** 40 | * @return A [ConstraintSet] cloned from the receiver. 41 | */ 42 | fun ConstraintLayout.toConstraintSet() = ConstraintSet().apply { clone(this@toConstraintSet) } 43 | -------------------------------------------------------------------------------- /app/src/main/java/rt/randamu/ResourcesExtensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("ResourceUtils") 2 | 3 | package rt.randamu 4 | 5 | import android.content.res.Resources 6 | import android.support.annotation.ArrayRes 7 | 8 | // Utility methods / extensions related to Resources 9 | 10 | /** 11 | * Retrieve an array consisting of resource IDs, a list of references to other resources. 12 | */ 13 | fun Resources.getResourceIdArray(@ArrayRes resId: Int): IntArray { 14 | val typedArray = obtainTypedArray(resId) 15 | val idArray = IntArray(typedArray.length()) 16 | for (i in 0 until typedArray.length()) { 17 | idArray[i] = typedArray.getResourceId(i, -1) 18 | } 19 | typedArray.recycle() 20 | return idArray 21 | } 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/rt/randamu/ViewDrawableExtensions.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("DrawableUtils") 2 | 3 | package rt.randamu 4 | 5 | import android.graphics.drawable.Drawable 6 | import android.support.annotation.ColorInt 7 | import android.support.v4.graphics.drawable.DrawableCompat 8 | import android.view.View 9 | 10 | // Utility methods / extensions related to Drawables 11 | 12 | /** 13 | * Tint this view's background with a given color. 14 | * 15 | * Utilizes [DrawableCompat] methods. 16 | */ 17 | fun View.tintBackground(@ColorInt tint: Int): Drawable { 18 | val drawable = DrawableCompat.wrap(background) 19 | DrawableCompat.setTint(drawable, tint) 20 | background = drawable 21 | return drawable 22 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/lina_dragon_slave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxhdpi/lina_dragon_slave.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/lina_fiery_soul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxhdpi/lina_fiery_soul.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/lina_laguna_blade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxhdpi/lina_laguna_blade.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/lina_light_strike_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxhdpi/lina_light_strike_array.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/lina_mcbeamish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxhdpi/lina_mcbeamish.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/overviewicon_agi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxhdpi/overviewicon_agi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/overviewicon_int.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxhdpi/overviewicon_int.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/overviewicon_str.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxhdpi/overviewicon_str.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_caspar_rubin_macbook_sql.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_caspar_rubin_macbook_sql.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_cory_seward_eggs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_cory_seward_eggs.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_danielle_macinnes_artichokes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_danielle_macinnes_artichokes.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_julia_d_alkmin_ice_tea.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_julia_d_alkmin_ice_tea.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_maria_fernanda_gonzalez_coffee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_maria_fernanda_gonzalez_coffee.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_michal_grosicki_glitter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_michal_grosicki_glitter.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_mike_kenneally_green_beans.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_mike_kenneally_green_beans.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_neven_krcmarek_grapes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_neven_krcmarek_grapes.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_nousnou_iwasaki_coffee_beans.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_nousnou_iwasaki_coffee_beans.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_oreo_cream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_oreo_cream.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_roberta_sorge_honey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_roberta_sorge_honey.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_roberta_sorge_pineapple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_roberta_sorge_pineapple.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_roberta_sorge_pomegranate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_roberta_sorge_pomegranate.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_shumilov_ludmila_pears.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_shumilov_ludmila_pears.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_tamarcus_brown_ruler.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_tamarcus_brown_ruler.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/unsplash_tatiana_lapina_macaroon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/queencodemonkey/cool-constraintlayout/250d132c5f4a824826f79ab15a7a7d9a0ad15d46/app/src/main/res/drawable-xxxhdpi/unsplash_tatiana_lapina_macaroon.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_android_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_downward_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_forward_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_upward_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dot_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fire_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_format_align_left_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_image_aspect_ratio_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_link_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_list_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_snowflake_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_spa_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_swap_horiz_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_view_list_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_widgets_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rect_stroke_grey.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_container_linear_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_container_scroll_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | 32 | 33 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_alignment.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 23 | 24 | 34 | 35 | 36 | 37 | 51 | 52 | 59 | 60 | 67 | 68 | 75 | 76 | 83 | 84 | 85 | 86 | 95 | 96 | 97 | 108 | 109 | 122 | 123 | 132 | 133 | 142 | 143 | 152 | 153 | 163 | 164 | 172 | 173 | 174 | 184 | 185 | 193 | 194 | 195 | 196 | 208 | 209 | 217 | 218 | 227 | 228 | 234 | 235 | 241 | 242 | 248 | 249 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_chains.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 22 | 23 | 31 | 32 | 40 | 41 | 42 | 43 | 55 | 56 | 67 | 68 | 79 | 80 | 91 | 92 | 93 | 94 | 105 | 106 | 117 | 118 | 129 | 130 | 141 | 142 | 153 | 154 | 155 | 156 | 169 | 170 | 181 | 182 | 193 | 194 | 205 | 206 | 217 | 218 | 219 | 220 | 233 | 234 | 245 | 246 | 257 | 258 | 269 | 270 | 281 | 282 | 283 | 284 | 297 | 298 | 309 | 310 | 322 | 323 | 334 | 335 | 347 | 348 | 357 | 358 |