Skip to content
This repository was archived by the owner on Dec 9, 2024. It is now read-only.

Commit e3653da

Browse files
committed
List > Loading
1 parent 29e7509 commit e3653da

File tree

13 files changed

+448
-5
lines changed

13 files changed

+448
-5
lines changed

Motion/app/build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ dependencies {
5656
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
5757
implementation 'androidx.transition:transition:1.2.0-beta01'
5858
implementation 'androidx.dynamicanimation:dynamicanimation:1.1.0-alpha02'
59-
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta02'
59+
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta03'
6060

6161
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
6262

@@ -65,6 +65,9 @@ dependencies {
6565
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
6666
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
6767

68+
def paging_version = '2.1.0'
69+
implementation "androidx.paging:paging-runtime-ktx:$paging_version"
70+
6871
implementation 'com.google.android.material:material:1.1.0-alpha09'
6972

7073
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"

Motion/app/src/main/AndroidManifest.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,22 @@
117117
android:resource="@array/stagger_apis" />
118118
</activity>
119119

120+
<activity
121+
android:name=".demo.loading.LoadingActivity"
122+
android:label="@string/loading_label">
123+
<intent-filter>
124+
<action android:name="android.intent.action.MAIN" />
125+
<category android:name="com.example.android.motion.intent.category.DEMO" />
126+
</intent-filter>
127+
128+
<meta-data
129+
android:name="com.example.android.motion.demo.DESCRIPTION"
130+
android:value="@string/loading_description" />
131+
<meta-data
132+
android:name="com.example.android.motion.demo.APIS"
133+
android:resource="@array/loading_apis" />
134+
</activity>
135+
120136
<activity
121137
android:name=".demo.oscillation.OscillationActivity"
122138
android:label="@string/oscillation_label"
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2019 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.android.motion.demo.loading
18+
19+
import android.animation.ObjectAnimator
20+
import android.os.SystemClock
21+
import android.view.LayoutInflater
22+
import android.view.View
23+
import android.view.ViewGroup
24+
import android.widget.ImageView
25+
import android.widget.TextView
26+
import androidx.core.animation.doOnEnd
27+
import androidx.paging.PagedListAdapter
28+
import androidx.recyclerview.widget.RecyclerView
29+
import com.bumptech.glide.Glide
30+
import com.bumptech.glide.load.resource.bitmap.CircleCrop
31+
import com.example.android.motion.R
32+
import com.example.android.motion.model.Cheese
33+
34+
internal class CheeseAdapter : PagedListAdapter<Cheese, CheeseViewHolder>(Cheese.DIFF_CALLBACK) {
35+
36+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheeseViewHolder {
37+
return CheeseViewHolder(parent)
38+
}
39+
40+
override fun onBindViewHolder(holder: CheeseViewHolder, position: Int) {
41+
val cheese: Cheese? = getItem(position)
42+
if (cheese == null) {
43+
holder.showPlaceholder()
44+
} else {
45+
holder.bind(cheese)
46+
}
47+
}
48+
}
49+
50+
/**
51+
* A dummy adapter that shows placeholders.
52+
*/
53+
internal class PlaceholderAdapter : RecyclerView.Adapter<CheeseViewHolder>() {
54+
55+
override fun getItemCount(): Int {
56+
return Int.MAX_VALUE
57+
}
58+
59+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheeseViewHolder {
60+
return CheeseViewHolder(parent)
61+
}
62+
63+
override fun onBindViewHolder(holder: CheeseViewHolder, position: Int) {
64+
// We have to call this method in onBindVH rather than onCreateVH because it uses the
65+
// adapterPosition of the ViewHolder.
66+
holder.showPlaceholder()
67+
}
68+
}
69+
70+
private const val FADE_DURATION = 1000L
71+
72+
internal class CheeseViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
73+
LayoutInflater.from(parent.context).inflate(R.layout.cheese_list_item, parent, false)
74+
) {
75+
val image: ImageView = itemView.findViewById(R.id.image)
76+
val name: TextView = itemView.findViewById(R.id.name)
77+
78+
/**
79+
* This is the animation we apply to each of the list items. It animates the alpha value from 1
80+
* to 0, then back to 1. The animation repeats infinitely until it is manually ended.
81+
*/
82+
private val animation = ObjectAnimator.ofFloat(itemView, View.ALPHA, 1f, 0f, 1f).apply {
83+
repeatCount = ObjectAnimator.INFINITE
84+
duration = FADE_DURATION
85+
// Reset the alpha on animation end.
86+
doOnEnd { itemView.alpha = 1f }
87+
}
88+
89+
fun showPlaceholder() {
90+
// Shift the timing of fade-in/out for each item by its adapter position. We use the
91+
// elapsed real time to make this independent from the timing of method call.
92+
animation.currentPlayTime =
93+
(SystemClock.elapsedRealtime() - adapterPosition * 30L) % FADE_DURATION
94+
animation.start()
95+
// Show the placeholder UI.
96+
image.setImageResource(R.drawable.image_placeholder)
97+
name.text = null
98+
name.setBackgroundResource(R.drawable.text_placeholder)
99+
}
100+
101+
fun bind(cheese: Cheese) {
102+
animation.end()
103+
Glide.with(image).load(cheese.image).transform(CircleCrop()).into(image)
104+
name.text = cheese.name
105+
name.setBackgroundResource(0)
106+
}
107+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2019 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.android.motion.demo.loading
18+
19+
import android.os.SystemClock
20+
import androidx.paging.DataSource
21+
import androidx.paging.PositionalDataSource
22+
import com.example.android.motion.model.Cheese
23+
24+
class CheeseDataSource : PositionalDataSource<Cheese>() {
25+
26+
companion object Factory : DataSource.Factory<Int, Cheese>() {
27+
override fun create(): DataSource<Int, Cheese> = CheeseDataSource()
28+
}
29+
30+
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Cheese>) {
31+
// Simulate a slow network.
32+
SystemClock.sleep(3000L)
33+
callback.onResult(
34+
Cheese.ALL.subList(
35+
params.requestedStartPosition,
36+
params.requestedStartPosition + params.requestedLoadSize
37+
),
38+
params.requestedStartPosition,
39+
Cheese.ALL.size
40+
)
41+
}
42+
43+
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Cheese>) {
44+
// Simulate a slow network.
45+
SystemClock.sleep(3000L)
46+
callback.onResult(
47+
Cheese.ALL.subList(
48+
params.startPosition, params.startPosition + params.loadSize
49+
)
50+
)
51+
}
52+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2019 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.android.motion.demo.loading
18+
19+
import android.os.Bundle
20+
import android.view.Menu
21+
import android.view.MenuItem
22+
import androidx.activity.viewModels
23+
import androidx.appcompat.app.AppCompatActivity
24+
import androidx.appcompat.widget.Toolbar
25+
import androidx.lifecycle.observe
26+
import androidx.recyclerview.widget.RecyclerView
27+
import androidx.transition.Fade
28+
import androidx.transition.TransitionManager
29+
import com.example.android.motion.R
30+
import com.example.android.motion.demo.FAST_OUT_SLOW_IN
31+
import com.example.android.motion.demo.transitionSequential
32+
import com.example.android.motion.ui.EdgeToEdge
33+
34+
/**
35+
* Shows a list of cheeses. We use the Paging Library to load the list.
36+
*/
37+
class LoadingActivity : AppCompatActivity() {
38+
39+
private val viewModel: LoadingViewModel by viewModels()
40+
41+
private lateinit var list: RecyclerView
42+
private val fade = transitionSequential {
43+
duration = 300L
44+
interpolator = FAST_OUT_SLOW_IN
45+
addTransition(Fade(Fade.OUT))
46+
addTransition(Fade(Fade.IN))
47+
}
48+
49+
private val placeholderAdapter = PlaceholderAdapter()
50+
private val cheeseAdapter = CheeseAdapter()
51+
52+
override fun onCreate(savedInstanceState: Bundle?) {
53+
super.onCreate(savedInstanceState)
54+
setContentView(R.layout.loading_activity)
55+
56+
val toolbar: Toolbar = findViewById(R.id.toolbar)
57+
list = findViewById(R.id.list)
58+
setSupportActionBar(toolbar)
59+
EdgeToEdge.setUpRoot(findViewById(R.id.coordinator))
60+
EdgeToEdge.setUpAppBar(findViewById(R.id.app_bar), toolbar)
61+
EdgeToEdge.setUpScrollingContent(list)
62+
63+
// Show the initial placeholders.
64+
// See the ViewHolder implementation for how to create the loading animation.
65+
list.adapter = placeholderAdapter
66+
viewModel.cheeses.observe(this) { cheeses ->
67+
if (list.adapter != cheeseAdapter) {
68+
list.adapter = cheeseAdapter
69+
TransitionManager.beginDelayedTransition(list, fade)
70+
}
71+
cheeseAdapter.submitList(cheeses)
72+
}
73+
}
74+
75+
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
76+
menuInflater.inflate(R.menu.loading, menu)
77+
return super.onCreateOptionsMenu(menu)
78+
}
79+
80+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
81+
return when (item.itemId) {
82+
R.id.action_refresh -> {
83+
TransitionManager.beginDelayedTransition(list, fade)
84+
list.adapter = placeholderAdapter
85+
viewModel.refresh()
86+
true
87+
}
88+
else -> super.onOptionsItemSelected(item)
89+
}
90+
}
91+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2019 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.android.motion.demo.loading
18+
19+
import androidx.lifecycle.LiveData
20+
import androidx.lifecycle.MediatorLiveData
21+
import androidx.lifecycle.ViewModel
22+
import androidx.paging.PagedList
23+
import androidx.paging.toLiveData
24+
import com.example.android.motion.model.Cheese
25+
26+
class LoadingViewModel : ViewModel() {
27+
28+
private var source: LiveData<PagedList<Cheese>>? = null
29+
private val _cheeses = MediatorLiveData<PagedList<Cheese>>()
30+
val cheeses: LiveData<PagedList<Cheese>> = _cheeses
31+
32+
init {
33+
refresh()
34+
}
35+
36+
fun refresh() {
37+
source?.let { _cheeses.removeSource(it) }
38+
val s = CheeseDataSource.toLiveData(pageSize = 15)
39+
source = s
40+
_cheeses.addSource(s) { _cheeses.postValue(it) }
41+
}
42+
}
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+
<!--
3+
~ Copyright 2019 The Android Open Source Project
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
<shape
18+
xmlns:android="http://schemas.android.com/apk/res/android"
19+
android:shape="oval">
20+
<solid android:color="@color/card_background" />
21+
</shape>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright 2019 The Android Open Source Project
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
<shape
18+
xmlns:android="http://schemas.android.com/apk/res/android"
19+
android:shape="rectangle">
20+
<size android:width="160dp" />
21+
<solid android:color="@color/card_background" />
22+
</shape>

Motion/app/src/main/res/layout/cheese_list_item.xml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@
3535

3636
<TextView
3737
android:id="@+id/name"
38-
android:layout_width="0dp"
39-
android:layout_height="match_parent"
38+
android:layout_width="wrap_content"
39+
android:layout_height="wrap_content"
40+
android:layout_gravity="center_vertical"
4041
android:layout_margin="@dimen/spacing_small"
41-
android:layout_weight="1"
42+
android:ellipsize="end"
4243
android:gravity="start|center_vertical"
44+
android:maxLines="1"
4345
android:textAppearance="?attr/textAppearanceListItem"
4446
tools:text="Cheese" />
4547

0 commit comments

Comments
 (0)