Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save ioannisa/44c839d12e4fa9e1f5b4d47f17252e5b to your computer and use it in GitHub Desktop.
Save ioannisa/44c839d12e4fa9e1f5b4d47f17252e5b to your computer and use it in GitHub Desktop.
Jetpack Compose RecyclerView with Sticky Header and network images using Coil
package eu.anifantakis.composeapp
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberImagePainter
import eu.anifantakis.composeapp.ui.theme.ComposeAppTheme
// =========================[ NOTE ]=========================
// add Coil for Jetpack Compose at your app dependencies
//
// implementation("io.coil-kt:coil-compose:1.3.2")
// ==========================================================
// define a simple data class to hold Person information
data class Person(
val id: Int,
val section: Int,
val name: String,
val imageUrl: String,
val landingPage: String
)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val persons = arrayListOf<Person>() // create an array list of persons
var section = 1
for (i in 1..200){ // create 200 Person instances in that list
if (i%15 == 0){
section++
}
persons.add(
Person(
id = i,
section = section, // all persons are assigned section
name = "Ioannis Anifantakis",
imageUrl = "https://anifantakis.eu/wp-content/uploads/2021/05/ioannis-anifantakis-firebase-small.jpg",
landingPage = "https://anifantakis.eu"
)
)
}
setContent {
ComposeAppTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
// [Demo1]: A Scrollable ListView equivalent
// ScrollableColumnDemo()
// [Demo2]: A RecyclerView equivalent
// LazyColumnDemo()
// [Demo3]: A RecyclerView equivalent with click listeners on its items
// LazyColumnClickableDemo{
// Toast.makeText(this, "Person $it", Toast.LENGTH_SHORT).show()
// }
// [Demo 4]: A RecyclerView equivalent with rich UI populating it's items from a dataset
// LazyColumnClickableAdvDemo(persons){
// Toast.makeText(this, "${it.name} ${it.id}", Toast.LENGTH_SHORT).show()
// }
// [Demo 5]: A RecyclerView equivalent with rich UI populating it's items from a dataset with a sticky header
LazyColumnClickableAdvStickyDemo(persons){
Toast.makeText(this, "${it.name} ${it.id}", Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
/**
* Demo1: Column
* A Scrollable ListView equivalent
*/
@Composable
fun ScrollableColumnDemo(){
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.verticalScroll(scrollState)
.fillMaxSize()
) {
for (i in 1..200){
Text(
text = "Person $i",
fontSize = 36.sp,
modifier = Modifier.padding(8.dp)
)
Divider(color = Color.Gray, thickness = 1.dp)
}
}
}
/**
* Demo2: LazyColumn
* A RecyclerView equivalent
*/
@Composable
fun LazyColumnDemo(){
LazyColumn(){
items(100){
Text(
text ="Person ${it+1}",
style = MaterialTheme.typography.h3,
modifier = Modifier.padding(8.dp)
)
Divider(color = Color.Gray, thickness = 1.dp)
}
}
}
/**
* Demo3 (bad): LazyColumn with clickable items
* A RecyclerView equivalent with click listeners on its items
*
* This is a "bad" approach, but works... we will build on the better version next
* Here we display a Toast using the LocalContext directly from inside the Composable Function
* This shows minimal change from the previous code, just to display how we can add a "clickable"
* functionality. Ofcourse handling business logic from inside the Composable Function is "bad"...
*
* Next we will create a delegation passing data back to the caller, and the caller should handle the
* business logic that displays the toast when the item is clicked ;)
*/
@Composable
fun LazyColumnClickableDemoBad() {
val context = LocalContext.current
LazyColumn() {
items(200){
Surface(modifier = Modifier.clickable { Toast.makeText(context, "Person $it", Toast.LENGTH_SHORT).show() }) {
Text(
text = "Person ${it + 1}",
fontSize = 36.sp,
modifier = Modifier.padding(8.dp)
)
Divider(color = Color.Gray, thickness = 1.dp)
}
}
}
}
/**
* Demo3: LazyColumn with clickable items
* A RecyclerView equivalent with click listeners on its items
* @selectedPerson: a lambda function to return back to the caller the number of the current item iteration on click
*/
@Composable
fun LazyColumnClickableDemo(selectedPerson: (Int) -> Unit){
LazyColumn(){
items(200){
Surface(modifier = Modifier.clickable { selectedPerson(it+1) }) {
Text(
text = "Person ${it+1}",
fontSize = 36.sp,
modifier = Modifier.padding(8.dp)
)
Divider(color = Color.Gray, thickness = 1.dp)
}
}
}
}
/**
* Used in Demo4 & Demo5
* Composable function to use Coil to download and display an image
* @imageUrl: The URL of the image
*/
@Composable
fun ImageLoader(imageUrl: String){
Image(
painter = rememberImagePainter(imageUrl),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(120.dp)
)
}
/**
* Used in Demo4 & Demo5
* Composable function to represent a list item
* @person: The Person instance, who's information will be displayed in the list item
* @selectedPerson: a lambda function to return back to the caller the Person instance on click
*/
@Composable
fun ListItem(person: Person, selectedPerson: (Person)->Unit){
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.clickable { selectedPerson(person) },
elevation = 8.dp,
) {
Row{
ImageLoader(person.imageUrl)
Spacer(modifier = Modifier.width(8.dp))
Text(
person.name+' '+person.id,
style = MaterialTheme.typography.h5,
modifier = Modifier.padding(8.dp)
)
}
}
}
/**
* Demo 4: LazyColumn displaying a List<Person> with clickable items
* A RecyclerView equivalent with rich UI populating it's items from a dataset
* @persons: a List<Person> passed as dataset
* @selectedPerson: a lambda function to return back to the caller the number of the current item iteration on click
*/
@Composable
fun LazyColumnClickableAdvDemo(persons: List<Person>, selectedPerson: (Person)->Unit){
LazyColumn(){
items(
items = persons,
itemContent = {
ListItem(person = it, selectedPerson = selectedPerson)
}
)
}
}
/**
* Demo 5: LazyColumn displaying a List<Person> with clickable items, utilizing a StickyHeader
* A RecyclerView equivalent with rich UI populating it's items from a dataset with a sticky header
* @persons: a List<Person> passed as dataset
* @selectedPerson: a lambda function to return back to the caller the number of the current item iteration on click
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun LazyColumnClickableAdvStickyDemo(persons: List<Person>, selectedPerson: (Person)->Unit){
val grouped = persons.groupBy{it.section}
LazyColumn(){
grouped.forEach { (section, sectionPersons) ->
stickyHeader {
Text(
text = "SECTION: $section",
color = Color.White,
modifier = Modifier
.background(color = Color.Black)
.padding(8.dp)
.fillMaxWidth()
)
}
items(
items = sectionPersons,
itemContent = {
ListItem(person = it, selectedPerson = selectedPerson)
}
)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment