Skip to content
This repository was archived by the owner on Feb 17, 2020. It is now read-only.

Commit 4e98966

Browse files
authored
Merge pull request #557 from cortinico/fab-hearth-avd
Replacing the FAB hearth icon with an AVD (hearth fill and break)
2 parents 218b583 + eaa7a16 commit 4e98966

14 files changed

Lines changed: 265 additions & 44 deletions

app/src/main/java/net/squanchy/eventdetails/widget/EventDetailsCoordinatorLayout.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ class EventDetailsCoordinatorLayout @JvmOverloads constructor(
2121
eventDetailsLayout.updateWith(event)
2222

2323
if (event.canBeFavorited) {
24-
val favoriteImage = if (event.favorite) R.drawable.ic_favorite_filled else R.drawable.ic_favorite_empty
25-
favoriteFab.setImageResource(favoriteImage)
24+
// Updates the FAB image state to trigger an AVD animation.
25+
val stateSet = intArrayOf(android.R.attr.state_checked * if (event.favorite) 1 else -1)
26+
favoriteFab.setImageState(stateSet, true)
2627
favoriteFab.setOnClickListener { listener.onFavoriteClick() }
2728
favoriteFab.isVisible = true
2829
} else {

app/src/main/java/net/squanchy/favorites/view/FavoritesSignedInEmptyLayout.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package net.squanchy.favorites.view
22

33
import android.content.Context
44
import android.os.Build
5-
import android.support.annotation.DrawableRes
65
import android.support.annotation.RequiresApi
76
import android.support.constraint.ConstraintLayout
87
import android.support.design.widget.Snackbar
@@ -30,22 +29,22 @@ class FavoritesSignedInEmptyLayout @JvmOverloads constructor(
3029
this.counter = counter
3130
}
3231

33-
override fun setButtonImage(@DrawableRes resId: Int) = favoriteFab.setImageResource(resId)
32+
override fun setButtonState(favorited: Boolean) {
33+
// Updates the FAB image state to trigger an AVD animation.
34+
val stateSet = intArrayOf(android.R.attr.state_checked * if (favorited) 1 else -1)
35+
favoriteFab.setImageState(stateSet, true)
36+
}
3437

3538
@RequiresApi(Build.VERSION_CODES.N)
3639
override fun showAchievement(message: String) {
3740
Snackbar.make(this, message.parseHtml(), Snackbar.LENGTH_LONG).show()
3841
}
3942

4043
private val favoriteButtonClickListener = View.OnClickListener {
41-
presentButtonIcon(counter, this, ::favoritesFilledIconId, ::favoritesEmptyIconId)
44+
presentButtonIcon(counter, this)
4245
presentAchievementMessage(counter, this, ::initialAchieventMessage, ::perseveranceAchievementMessage)
4346
}
4447

45-
private fun favoritesFilledIconId() = R.drawable.ic_favorite_filled
46-
47-
private fun favoritesEmptyIconId() = R.drawable.ic_favorite_empty
48-
4948
private fun initialAchieventMessage() = resources.getString(R.string.favorites_achievement_fast_learner)
5049

5150
private fun perseveranceAchievementMessage() = resources.getString(R.string.favorites_achievement_persevering)

app/src/main/java/net/squanchy/favorites/view/FavoritesSignedInEmptyLayoutPresenter.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ private const val TAPS_TO_TRIGGER_INITIAL_ACHIEVEMENT = 5
44
private const val TAPS_TO_TRIGGER_PERSEVERANCE_ACHIEVEMENT = 15
55

66
private typealias AchievementMessageProvider = () -> String
7-
private typealias ResIdProvider = () -> Int
87

9-
internal fun presentButtonIcon(counter: Int, view: FavoritesSignedInEmptyLayoutView, filledIconId: ResIdProvider, emptyIconId: ResIdProvider) {
10-
11-
if (counter % 2 == 0) view.setButtonImage(filledIconId()) else view.setButtonImage(emptyIconId())
12-
}
8+
internal fun presentButtonIcon(
9+
counter: Int,
10+
view: FavoritesSignedInEmptyLayoutView
11+
) = view.setButtonState(counter % 2 == 0)
1312

1413
internal fun presentAchievementMessage(
1514
counter: Int,
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package net.squanchy.favorites.view
22

3-
import android.support.annotation.DrawableRes
4-
53
interface FavoritesSignedInEmptyLayoutView {
64

75
fun updateCounter(counter: Int)
86

9-
fun setButtonImage(@DrawableRes resId: Int)
7+
fun setButtonState(favorited: Boolean)
108

119
fun showAchievement(message: String)
1210
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<set xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<!-- First reset state as the animation is delayed. -->
5+
<objectAnimator
6+
android:duration="0"
7+
android:propertyName="trimPathEnd"
8+
android:valueFrom="0"
9+
android:valueTo="0"/>
10+
11+
<objectAnimator
12+
android:duration="0"
13+
android:propertyName="strokeAlpha"
14+
android:valueFrom="0.4"
15+
android:valueTo="0.4"/>
16+
17+
<!-- Now trim-reveal & fade in the heart path. -->
18+
<objectAnimator
19+
android:duration="300"
20+
android:interpolator="@android:interpolator/fast_out_slow_in"
21+
android:propertyName="trimPathEnd"
22+
android:startOffset="400"
23+
android:valueFrom="0"
24+
android:valueTo="1"/>
25+
26+
<objectAnimator
27+
android:duration="300"
28+
android:interpolator="@android:interpolator/fast_out_slow_in"
29+
android:propertyName="strokeAlpha"
30+
android:startOffset="400"
31+
android:valueFrom="0.4"
32+
android:valueTo="1"/>
33+
34+
</set>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<set
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:ordering="sequentially">
5+
6+
<!-- First reset state. -->
7+
<objectAnimator
8+
android:duration="0"
9+
android:propertyName="fillAlpha"
10+
android:valueFrom="1"
11+
android:valueTo="1"/>
12+
13+
<objectAnimator
14+
android:duration="300"
15+
android:interpolator="@android:interpolator/linear_out_slow_in"
16+
android:propertyName="fillAlpha"
17+
android:startOffset="100"
18+
android:valueFrom="1"
19+
android:valueTo="0"/>
20+
21+
</set>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<!-- Animated selector for the Favourite FAB icon -->
5+
6+
<item
7+
android:id="@+id/liked"
8+
android:drawable="@drawable/ic_favorite_filled"
9+
android:state_checked="true"/>
10+
11+
<item
12+
android:id="@+id/not_liked"
13+
android:drawable="@drawable/ic_favorite_empty"/>
14+
15+
<transition
16+
android:drawable="@drawable/avd_ic_favorite_fill"
17+
android:fromId="@id/not_liked"
18+
android:toId="@id/liked"/>
19+
20+
<transition
21+
android:drawable="@drawable/avd_ic_favorite_break"
22+
android:fromId="@id/liked"
23+
android:toId="@id/not_liked"/>
24+
25+
</animated-selector>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<animated-vector
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:aapt="http://schemas.android.com/aapt"
5+
android:drawable="@drawable/ic_favorite_break">
6+
7+
<!-- Rotate and fade out the left half. -->
8+
<target android:name="broken_heart_left_group">
9+
<aapt:attr name="android:animation">
10+
<objectAnimator
11+
android:duration="400"
12+
android:interpolator="@android:interpolator/linear_out_slow_in"
13+
android:propertyName="rotation"
14+
android:valueFrom="0"
15+
android:valueTo="-20"/>
16+
</aapt:attr>
17+
</target>
18+
19+
<target
20+
android:name="broken_heart_left"
21+
android:animation="@animator/animset_ic_favorite_fade_out"/>
22+
23+
<!-- Rotate and fade out the right half. -->
24+
<target android:name="broken_heart_right_group">
25+
<aapt:attr name="android:animation">
26+
<objectAnimator
27+
android:duration="400"
28+
android:interpolator="@android:interpolator/linear_out_slow_in"
29+
android:propertyName="rotation"
30+
android:valueFrom="0"
31+
android:valueTo="20"/>
32+
</aapt:attr>
33+
</target>
34+
35+
<target
36+
android:name="broken_heart_right"
37+
android:animation="@animator/animset_ic_favorite_fade_out"/>
38+
39+
<!-- Re-draw the strokes. -->
40+
<target
41+
android:name="heart_stroke_left_atrium"
42+
android:animation="@animator/animset_ic_favorite_draw_stroke"/>
43+
44+
<target
45+
android:name="heart_stroke_right_atrium"
46+
android:animation="@animator/animset_ic_favorite_draw_stroke"/>
47+
48+
</animated-vector>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<animated-vector
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:aapt="http://schemas.android.com/aapt"
5+
android:drawable="@drawable/ic_favorite_filled">
6+
7+
<!-- Fill the hearth -->
8+
<target android:name="clip">
9+
<aapt:attr name="android:animation">
10+
<objectAnimator
11+
android:duration="@android:integer/config_mediumAnimTime"
12+
android:interpolator="@android:interpolator/fast_out_slow_in"
13+
android:propertyName="pathData"
14+
android:valueFrom="M18 37 L38 37 L38 37 L18 37 Z"
15+
android:valueTo="M0 0 L56 0 L56 56 L0 56 Z"
16+
android:valueType="pathType"/>
17+
</aapt:attr>
18+
</target>
19+
20+
</animated-vector>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<vector
2+
xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:width="48dp"
4+
android:height="48dp"
5+
android:tint="?attr/colorControlNormal"
6+
android:viewportHeight="56"
7+
android:viewportWidth="56">
8+
9+
<!-- Stroked heart, 2 paths so can 'draw' them concurrently by animating trim. Initially hidden -->
10+
<path
11+
android:name="heart_stroke_left_atrium"
12+
android:pathData="M28.7194825,38.2960196 L25.6688233,35.5523072 C21.6213379,31.793335 18.0159912,28.8906251 18.0159912,24.845337 C18.0159912,21.5882568 20.6305373,19.9651315 23.6337891,19.9651442 C24.9985352,19.96515 26.7993165,21.180664 28.643982,23.1297608"
13+
android:strokeColor="@android:color/white"
14+
android:strokeWidth="2"
15+
android:trimPathEnd="0"/>
16+
17+
<path
18+
android:name="heart_stroke_right_atrium"
19+
android:pathData="M27.2310342,38.2944327 L30.7645263,35.2004395 C34.8336142,31.2354829 37.751709,29.1181308 38.0040283,25.0838624 C38.1681943,22.4590544 35.7730552,20.034668 33.3791503,20.034668 C30.4320068,20.034668 29.671997,21.0472412 27.2310342,23.1328126"
20+
android:strokeColor="@android:color/white"
21+
android:strokeWidth="2"
22+
android:trimPathEnd="0"/>
23+
24+
<!-- Left broken heart, need group to set pivot for rotation anim. -->
25+
<group
26+
android:name="broken_heart_left_group"
27+
android:pivotX="28"
28+
android:pivotY="37.3">
29+
30+
<path
31+
android:name="broken_heart_left"
32+
android:fillColor="@android:color/white"
33+
android:pathData="M28.0307583,21.0541825 C28.020455,21.0660803 28.0102021,21.0780196 28,21.09 C26.91,19.81 25.24,19 23.5,19 C20.42,19 18,21.42 18,24.5 C18,28.28 21.4,31.36 26.55,36.03 L28,37.35 L28.0022188,37.3479954 L27.7813721,36.988221 L28.4886923,36.0733956 L27.5061678,34.7635697 L28.7816611,33.0267266 L26.9441528,31.0075072 L29.1486431,28.7245062 L27.1169819,27.143298 L29.1486431,25.0178865 L26.4880371,22.9773849 L28.0307583,21.0541825 L28.0307583,21.0541825 Z"/>
34+
35+
</group>
36+
37+
<!-- Right broken heart. -->
38+
<group
39+
android:name="broken_heart_right_group"
40+
android:pivotX="28"
41+
android:pivotY="37.3">
42+
43+
<path
44+
android:name="broken_heart_right"
45+
android:fillColor="@android:color/white"
46+
android:pathData="M28.0307583,21.0541825 C28.1689169,20.8946428 28.3161363,20.7425565 28.4714083,20.5985959 L28.9146818,20.226173 C29.9263704,19.4567073 31.193451,19 32.5,19 C35.58,19 38,21.42 38,24.5 C38,28.28 34.6,31.36 29.45,36.04 L28.0022188,37.3479954 L27.7813721,36.988221 L28.4886923,36.0733956 L27.5061678,34.7635697 L28.7816611,33.0267266 L26.9441528,31.0075072 L29.1486431,28.7245062 L27.1169819,27.143298 L29.1486431,25.0178865 L26.4880371,22.9773849 L28.0307583,21.0541825 L28.0307583,21.0541825 Z"/>
47+
48+
</group>
49+
50+
</vector>

0 commit comments

Comments
 (0)