Skip to content
This repository was archived by the owner on Jul 11, 2025. It is now read-only.

Commit 853b75f

Browse files
committed
Code cleanup for existing samples:
- Bump to Motion Layout 2.0 stable - ViewPagerActivity.kt to use custom type directly instead of casting - CollapsingToolbar.kt added comments and cleaned up inset code - Entrance.kt removed saved state code due to bug. Removed Ken Burns effect. Replay animation on rotation for now - activity_main.xml styled preview cards
1 parent e0c3eb0 commit 853b75f

10 files changed

Lines changed: 252 additions & 162 deletions

File tree

ConstraintLayoutExamples/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ buildscript {
2727
targetSdkVersion = 28
2828

2929
appCompatVersion = '1.3.0-alpha01'
30-
constraintLayoutVersion = '2.0.0-beta8'
30+
constraintLayoutVersion = '2.0.0'
3131
emojiBundledVersion = '1.1.0'
3232
glideVersion = '4.8.0'
33-
kotlinVersion = '1.3.72'
33+
kotlinVersion = '1.4.0'
3434
lifeCycleVersion = '2.2.0'
3535
lottieVersion = '2.5.1'
3636
materialVersion = '1.3.0-alpha02'

ConstraintLayoutExamples/motionlayout/src/main/java/com/google/androidstudio/motionlayoutexample/viewpagerdemo/ViewPagerActivity.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ package com.google.androidstudio.motionlayoutexample.viewpagerdemo
1919
import android.os.Bundle
2020
import androidx.appcompat.app.AppCompatActivity
2121
import androidx.constraintlayout.motion.widget.MotionLayout
22-
import androidx.viewpager.widget.ViewPager
2322
import com.google.androidstudio.motionlayoutexample.R
23+
import com.google.androidstudio.motionlayoutexample.utils.ViewpagerHeader
2424
import kotlinx.android.synthetic.main.motion_16_viewpager.*
2525

2626
class ViewPagerActivity : AppCompatActivity() {
@@ -29,23 +29,23 @@ class ViewPagerActivity : AppCompatActivity() {
2929
super.onCreate(savedInstanceState)
3030
val layout = R.layout.motion_16_viewpager
3131
setContentView(layout)
32-
val motionLayout = findViewById<MotionLayout>(R.id.motionLayout)
32+
val viewPagerHeader = findViewById<ViewpagerHeader>(R.id.motionLayout)
3333

3434
val adapter = ViewPagerAdapter(supportFragmentManager)
3535
adapter.addPage("Page 1", R.layout.motion_16_viewpager_page1)
3636
adapter.addPage("Page 2", R.layout.motion_16_viewpager_page2)
3737
adapter.addPage("Page 3", R.layout.motion_16_viewpager_page3)
3838
pager.adapter = adapter
3939
tabs.setupWithViewPager(pager)
40-
if (motionLayout != null) {
41-
pager.addOnPageChangeListener(motionLayout as ViewPager.OnPageChangeListener)
40+
if (viewPagerHeader != null) {
41+
pager.addOnPageChangeListener(viewPagerHeader)
4242
}
4343

4444
val debugMode = if (intent.getBooleanExtra("showPaths", false)) {
4545
MotionLayout.DEBUG_SHOW_PATH
4646
} else {
4747
MotionLayout.DEBUG_SHOW_NONE
4848
}
49-
motionLayout.setDebugMode(debugMode)
49+
viewPagerHeader.setDebugMode(debugMode)
5050
}
5151
}

ConstraintLayoutExamples/motionlayoutintegrations/src/main/java/com/example/androidstudio/motionlayoutintegrations/CollapsingToolbar.kt

Lines changed: 89 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,59 +8,91 @@ import android.os.Bundle
88
import android.util.AttributeSet
99
import android.util.TypedValue
1010
import android.view.View
11+
import android.view.Window
1112
import android.view.WindowManager
1213
import androidx.core.view.ViewCompat
1314
import androidx.core.view.WindowInsetsCompat
1415
import com.example.androidstudio.motionlayoutintegrations.databinding.ActivityCollapsingToolbarBinding
1516
import com.google.android.material.appbar.AppBarLayout
17+
import kotlinx.android.synthetic.main.activity_entrance.*
1618

1719
class CollapsingToolbar : AppCompatActivity() {
1820

1921
override fun onCreate(savedInstanceState: Bundle?) {
2022
super.onCreate(savedInstanceState)
21-
window.apply {
22-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
23-
clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
24-
}
25-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
26-
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
27-
statusBarColor = Color.TRANSPARENT
28-
}
29-
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
30-
}
23+
24+
window.goEdgeToEdge()
3125

3226
val binding = ActivityCollapsingToolbarBinding.inflate(layoutInflater)
3327
setContentView(binding.root)
3428

29+
// When the AppBarLayout progress changes, snap MotionLayout to the current progress
3530
val listener = AppBarLayout.OnOffsetChangedListener { appBar, verticalOffset ->
31+
// convert offset into % scrolled
3632
val seekPosition = -verticalOffset / appBar.totalScrollRange.toFloat()
33+
// inform both both MotionLayout and CutoutImage of the animation progress.
3734
binding.motionLayout.progress = seekPosition
3835
binding.background.translationProgress = (100 * seekPosition).toInt()
3936
}
4037
binding.appbarLayout.addOnOffsetChangedListener(listener)
4138

39+
// get the collapsed height from the motion layout specified in XML
40+
val desiredToolbarHeight = binding.motionLayout.minHeight
41+
42+
// Set two guidelines in the collapsed state for displaying a scrim based on the inset. Also
43+
// resize the MotionLayout when collapsed to add the inset height.
44+
//
45+
// You could also set a similar inset guide for the expanded state if your animation uses
46+
// the top of the screen when expanded.
4247
ViewCompat.setOnApplyWindowInsetsListener(binding.motionLayout) { _, insets: WindowInsetsCompat ->
43-
val collapsedTop = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100f, resources.displayMetrics).toInt()
48+
// resize the motionLayout in collapsed state to add the needed inset height
49+
val insetTopHeight = insets.systemWindowInsetTop
50+
binding.motionLayout.minimumHeight = desiredToolbarHeight + insetTopHeight
51+
52+
// modify the end ConstraintSet to set a guideline at the top and bottom of inset
4453
val endConstraintSet = binding.motionLayout.getConstraintSet(R.id.end)
45-
endConstraintSet.setGuidelineEnd(R.id.collapsed_top, collapsedTop)
46-
endConstraintSet.setGuidelineEnd(R.id.inset, collapsedTop - insets.systemWindowInsetTop)
54+
// this guideline is the bottom of the inset area
55+
endConstraintSet.setGuidelineEnd(R.id.inset, desiredToolbarHeight)
56+
// this guideline is the top of the inset area (top of screen)
57+
endConstraintSet.setGuidelineEnd(R.id.collapsed_top, desiredToolbarHeight + insetTopHeight)
58+
4759
insets
4860
}
4961
}
50-
}
5162

52-
private val INFLECTION_PART = 8
53-
private val PI_OVER_2 = Math.PI / 2
63+
private fun Window.goEdgeToEdge() {
64+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
65+
clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
66+
}
67+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
68+
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
69+
statusBarColor = Color.TRANSPARENT
70+
}
71+
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
72+
}
73+
}
5474

75+
/**
76+
* A custom view to display a circular cutout on an image that can be controlled by MotionLayout.
77+
*
78+
* Animation of this view is driven by motionLayout controlling [bottomCutSize] and [endCutSize]
79+
* and [translationProgress].
80+
*
81+
* This View will overwrite scaleType from XML to be matrix to allow custom translation.
82+
*/
5583
class CutoutImage @JvmOverloads constructor(
56-
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
84+
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
5785
) : androidx.appcompat.widget.AppCompatImageView(context, attrs, defStyleAttr) {
5886

5987
private val scratchRect = RectF()
6088

6189
private var _bottomCutSize: Float
6290

63-
// this is just to make Kotlin happy
91+
/**
92+
* Set the size of the bottomCut.
93+
*
94+
* This can directly be called by MotionLayout to animate the size.
95+
*/
6496
var bottomCutSize: Float
6597
get() = _bottomCutSize
6698
set(value) {
@@ -69,13 +101,27 @@ class CutoutImage @JvmOverloads constructor(
69101
}
70102

71103
private var _endCutSize: Float
104+
105+
/**
106+
* Set the size of the endCut.
107+
*
108+
* This can directly be called by MotionLayout to animate the size.
109+
*/
72110
var endCutSize: Float
73111
get() = _endCutSize
74112
set(value) {
75113
_endCutSize = value
76114
invalidate()
77115
}
78116

117+
/**
118+
* Fixed image translation progress to make the image scroll as animation progresses.
119+
*
120+
* This uses a Matrix to scale then translate the image based on the current progress.
121+
*
122+
* This can be directly called by MotionLayout, or be called in response to progress change like
123+
* we do in this sample.
124+
*/
79125
var translationProgress: Int = 0
80126
set(value) {
81127
field = value
@@ -90,10 +136,13 @@ class CutoutImage @JvmOverloads constructor(
90136
private val painter = Paint()
91137

92138
private val grayPainter = Paint().also {
93-
it.color = 0x33000000.toInt()
139+
it.color = 0x33000000
94140
it.strokeWidth = dpToF(1)
95141
}
96142

143+
/**
144+
* Read the endCut, bottomCut, and cutoutColor from XML
145+
*/
97146
init {
98147
val typedArray = context.theme.obtainStyledAttributes(
99148
attrs,
@@ -108,6 +157,9 @@ class CutoutImage @JvmOverloads constructor(
108157
typedArray.recycle()
109158
}
110159

160+
/**
161+
* Force the scaleType to matrix
162+
*/
111163
init {
112164
scaleType = ScaleType.MATRIX // ignore any other scale types
113165
}
@@ -118,45 +170,39 @@ class CutoutImage @JvmOverloads constructor(
118170
resources.displayMetrics
119171
)
120172

173+
/**
174+
* Draw the image with current cutouts applied
175+
*/
121176
override fun onDraw(canvas: Canvas?) {
122177
// let the parent draw the bitmap
123178
super.onDraw(canvas)
124179

180+
// draw the bottom circle at the correct position and size
125181
canvas?.drawCircle(
126-
width.toFloat() / 2,
127-
height.toFloat(),
128-
_bottomCutSize / 2,
182+
width.toFloat() / 2, // midpoint of view
183+
height.toFloat(), // bottom of view
184+
_bottomCutSize / 2, // radius from diameter
129185
painter
130186
)
131187

188+
// draw the end circle at the correct position and size
132189
val margin = dpToF(16)
133-
if (height.toFloat() <= _endCutSize) {
134-
// this is to fill in the area to the right of the circle to avoid showing a small
135-
// triangle of background in (bottom right & top left) during expansion
136-
val centerV = 2 * height.toFloat() / 3
137-
scratchRect.set(
138-
width - margin,
139-
centerV - _endCutSize / 2,
140-
width.toFloat(),
141-
centerV + _endCutSize / 2
142-
)
143-
canvas?.drawRect(scratchRect, painter)
144-
}
145-
146190
canvas?.drawCircle(
147-
width - margin,
148-
2 * height.toFloat() / 3,
149-
_endCutSize / 2,
191+
width - margin, // end of view, with custom margin applied
192+
2 * height.toFloat() / 3, // 2/3 down on view (determined by designer)
193+
_endCutSize / 2, // radius from diameter
150194
painter
151195
)
152196

153-
// add a 1px gray line to the bottom of the circle region so it clearly divides from
154-
// surrounding region
197+
// add a 1px gray line to the bottom of the end circle region so it clearly divides from
198+
// surrounding region (this effectively brings the shadow in early on the end circle)
155199
canvas?.drawLine(
200+
// start at the left edge of circle (this could do trig to calculate intersection
201+
// between circle and bottom, but visually this works fine)
156202
width - margin - _endCutSize / 2,
157-
height.toFloat(),
158-
width.toFloat(),
159-
height.toFloat(),
203+
height.toFloat(), // bottom of view
204+
width.toFloat(), // to end of view X
205+
height.toFloat(), // bottom of view
160206
grayPainter
161207
)
162208
}

ConstraintLayoutExamples/motionlayoutintegrations/src/main/java/com/example/androidstudio/motionlayoutintegrations/Entrance.kt

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,5 @@ class Entrance : AppCompatActivity() {
1414
super.onCreate(savedInstanceState)
1515
binding = ActivityEntranceBinding.inflate(layoutInflater)
1616
setContentView(binding.root)
17-
18-
savedInstanceState?.let {
19-
20-
}
21-
}
22-
23-
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
24-
super.onRestoreInstanceState(savedInstanceState)
25-
val motionLayoutState = savedInstanceState.getBundle(SIS_MOTION_LAYOUT_STATE)
26-
motionLayoutState?.let {
27-
binding.motionLayout.transitionState = motionLayoutState
28-
}
29-
}
30-
31-
override fun onSaveInstanceState(outState: Bundle) {
32-
super.onSaveInstanceState(outState)
33-
outState.putBundle(SIS_MOTION_LAYOUT_STATE, binding.motionLayout.transitionState)
3417
}
3518
}
37.6 KB
Loading
52.8 KB
Loading

0 commit comments

Comments
 (0)