Stop toolbar to collapse when contents are within the screen size - android

I am following this document and trying to use compose view in XML. But my composeView is not a LazyColumn. It's a normal Column component.
With the following code the Toolbar is collapsing as expected when the contents are more than the screen size. But also collapses when the contents are within the screen size. How do I avoid ToolBar collapsing when the contents are within the screen size?
XML Code
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="100dp"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|enterAlways|snap">
<androidx.compose.ui.platform.ComposeView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/top_app_bar_compose" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView
android:id="#+id/compose_view"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And here is my Jetpack compose code
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun CollapsingTopAppBarWithContentsView(
TopAppBarType: TopAppBarType,
backgroundColor: Color,
content: #Composable () -> Unit
) {
val topAppBarHeight = 84.dp
AndroidView(modifier = Modifier
.background(backgroundColor),
factory = {
val view = LayoutInflater.from(it).inflate(R.layout.layout_top_app_bar_with_compose, null, false)
val topAppBarComposeView = view.findViewById<ComposeView>(R.id.top_app_bar_compose)
topAppBarComposeView.setContent {
TopAppBarView(
modifier = Modifier
.fillMaxWidth()
.background(backgroundColor)
.height(topAppBarHeight),
TopAppBarType = TopAppBarType
)
}
view.findViewById<ComposeView>(R.id.compose_view).apply {
setContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection()
// Add the nested scroll connection to your top level #Composable element
// using the nestedScroll modifier.
Column(
modifier = Modifier
.nestedScroll(nestedScrollInterop)
.verticalScroll(state = rememberScrollState())
) {
//column content
// Header
Text(
text = "Header",
modifier = Modifier
.padding(horizontal = 30.dp)
.wrapContentWidth()
.semantics { heading() },
fontSize = 26.sp,
color = colorResource(id = R.color.johnstonBlack)
)
Text(
text = "Title comes here",
modifier = Modifier
.padding(start = 30.dp, end = 30.dp, top = 38.dp)
.wrapContentWidth()
.semantics { heading() },
fontSize = 21.sp,
color = colorResource(id = R.color.johnstonBlack)
)
Text(
text = "Long Long Long description comes here",
modifier = Modifier
.padding(start = 30.dp, end = 30.dp, top = 13.dp)
.wrapContentWidth(),
fontSize = 16.sp,
color = colorResource(id = R.color.johnstonBlack)
)
}
}
}
view
})
}

Related

How to set values for AbstractComposeView using XML?

I have a Composable wrapped in AbstractComposeView to be used in XML.
How to set values to this view.
Example,
Composable and AbstractComposeView
class MyComposeView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : AbstractComposeView(context, attrs, defStyleAttr) {
private var titleText by mutableStateOf("Default text")
var titleValue: String
get() = titleText
set(value) {
titleText = value
}
#Composable
override fun Content() {
ListItem(
text = titleText,
)
}
}
#Composable
fun ListItem(
text: String,
) {
Row(
modifier = Modifier,
) {
Text(
text = text,
)
}
}
XML
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ViewActivity">
<com.example.android.MyComposeView
android:id="#+id/my_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Activity
findViewById<MyComposeView>(R.id.my_view).titleValue = "From Activity"
This gives the intended result, but how to achieve the same without any code changes in the activity?
Something like app:titleText? (Tried this didn't work).
As for a regular custom view, you will need to rely on attribut set.
First create it in res > values > attrs.xml
Somethings like:
<resources>
<declare-styleable name="MyComposeView">
<attr name="titleText" format="string" />
</declare-styleable>
</resources>
Then apply it in your MyComposeView class
init {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.MyComposeView,
0, 0).apply {
try {
titleText = attributes.getString(R.styleable.MyComposeView_ titleText).toString()
} finally {
recycle()
}
}
}
Références:
https://developer.android.com/develop/ui/views/layout/custom-views/create-view#customattr
https://developer.android.com/develop/ui/views/layout/custom-views/create-view#applyattr

Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed

I am using ComposeView inside my recyclerview item layout to work with jetpack compose. I am getting weird issue when I open screen
Error
java.lang.IllegalStateException: Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container.
I tried to follow this stack overflow but it didn't work
main_activity.xml
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.compose.ui.platform.ComposeView
android:id="#+id/itemComposable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
viewholder.kt
class OptionsViewHolder(val binding: ItemLayoutBinding) : Recyclerview.ViewHolder(binding.root) {
private val context = binding.root.context
companion object {
fun from(parent: ViewGroup): OptionsViewHolder {
return OptionsViewHolder(
ItemLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
}
fun bindChoice() {
binding.itemComposable.setContent {
BoxWithConstraints {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.height(this#BoxWithConstraints.maxHeight)
.verticalScroll(rememberScrollState())
) {
items(getOptions()) { option ->
Text(text = option)
}
}
}
}
}
private fun getOptions() = mutableListOf(
context.getString(R.string.monitor),
context.getString(R.string.pressure),
context.getString(R.string.not_sure)
)
}
You are using a LazyColumn in a RecyclerView which is not allowed. A LazyColumn is the equivalent of a RecyclerView in Compose. So you are nesting RecyclerViews or LazyColumns.

Bottom Navigation in Compose

I want to create BottomNavigation with two items. Screen for each item is build in Compose.
My LeadActivity layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph"
tools:ignore="FragmentTagUsage" />
</FrameLayout>
NavigationGraph:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_graph"
app:startDestination="#+id/home_fragment">
<fragment
android:id="#+id/home_fragment"
android:name="com.app.android.rate.home.ui.Home.HomeFragment"
android:label="Home" />
<fragment
android:id="#+id/profile_fragment"
android:name="com.app.android.rate.home.ui.profile.ProfileFragment"
android:label="Profile"></fragment>
</navigation>
LeadActivity:
class LeadActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
RateMeAppTheme {
ContentScreen()
}
}
}
companion object {
fun start(context: Context) =
Intent(context, LeadActivity::class.java).let(context::startActivity)
}
}
My Composable function:
#Composable
fun ContentScreen() {
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState()
var currentScreen by remember { mutableStateOf(Screens.Home) }
val bottomBar: #Composable () -> Unit = {
if (currentScreen == Screens.Home) {
BottomBar(
navController = navController,
screens = screenList
)
}
}
Scaffold(
bottomBar = {
bottomBar()
},
scaffoldState = scaffoldState,
drawerContent = {},
drawerGesturesEnabled = scaffoldState.drawerState.isOpen,
) { innerPadding ->
NavigationHost(navController = navController)
}
}
#Composable
fun BottomBar(
modifier: Modifier = Modifier,
screens: List<Screens>,
navController: NavController,
) {
BottomNavigation(modifier = modifier) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
screens.forEach { screen ->
BottomNavigationItem(
icon = { Icon(imageVector = screen.icon, contentDescription = screen.title) },
label = { Text(screen.title) },
selected = currentRoute == screen.title,
onClick = {
}
)
}
}
}
#Composable
fun NavigationHost(navController: NavController) {
val homeScreen = Screens.Home.title
NavHost(
navController = navController as NavHostController,
startDestination = homeScreen
) {
composable(homeScreen) {
navController.navigate(R.id.home_fragment)
}
composable(Screens.Profile.title) {
navController.navigate(R.id.profile_fragment)
}
}
}
Currently I get error:
Navigation action/destination com.app.android.rate:id/home_fragment cannot be found from the current destination Destination.
Should I find NavController in LeadActivity and pass it to #ContentScreen()?
You don't put navController.navigate() inside the composable function, you put a Composable view

RecyclerView - Alphabet index not appearing

As part of my RecyclerView, I was expecting a row of letters to appear underneath it but for some reason the row is not appearing despite setting the properties to show it on screen.
Kotlin activity
class MainActivity : AppCompatActivity() {
private lateinit var adapterFruit: AdapterFruit
private lateinit var adapterAlphabet: AdapterAlphabet
private val arrayItemsFruit = ArrayList<ItemFruit>()
private val arrayItemsBtns = ArrayList<ItemAlphabet>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mToolbar = findViewById<Toolbar>(R.id.myToolbar)
val mRecyclerViewV = findViewById<RecyclerView>(R.id.mRecyclerViewWithToolbarV)
val mRecyclerViewH = findViewById<RecyclerView>(R.id.mRecyclerViewWithToolbarH)
// ...Do other stuff here
setSupportActionBar(mToolbar)
val mTitle = findViewById<TextView>(R.id.myToolbar_title)
mTitle.text = getString(R.string.fruit)
// Alphabet array
arrayItemsBtns.add(ItemAlphabet("A"))
arrayItemsBtns.add(ItemAlphabet("B"))
arrayItemsBtns.add(ItemAlphabet("C"))
arrayItemsBtns.add(ItemAlphabet("D"))
arrayItemsBtns.add(ItemAlphabet("F"))
arrayItemsBtns.add(ItemAlphabet("G"))
arrayItemsBtns.add(ItemAlphabet("K"))
arrayItemsBtns.add(ItemAlphabet("L"))
arrayItemsBtns.add(ItemAlphabet("M"))
arrayItemsBtns.add(ItemAlphabet("O"))
arrayItemsBtns.add(ItemAlphabet("P"))
arrayItemsBtns.add(ItemAlphabet("Q"))
arrayItemsBtns.add(ItemAlphabet("R"))
arrayItemsBtns.add(ItemAlphabet("S"))
arrayItemsBtns.add(ItemAlphabet("T"))
arrayItemsBtns.add(ItemAlphabet("W"))
// Fruit array items
arrayItemsFruit.add(
ItemFruit(
getString(R.string.apple)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.blackberry)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.cherry)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.date)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.fig)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.grapefruit)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.kiwi)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.lemon)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.mango)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.pineapple)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.quince)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.raspberry)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.strawberry)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.tomato)
)
)
arrayItemsFruit.add(
ItemFruit(
getString(R.string.watermelon)
)
)
// Set Vertical RecyclerView
val isScreenSmall = resources.getBoolean(R.bool.isScreenSmall)
if (isScreenSmall) {
// Use special item decoration for small devices
mRecyclerViewV.layoutManager =
LinearLayoutManager(this)
val mListener = AdapterView.OnItemClickListener { _, _, _, _ -> }
adapterFruit = AdapterFruit(arrayItemsFruit, mListener)
mRecyclerViewV.addItemDecoration(
androidx.recyclerview.widget.DividerItemDecoration(
this,
LinearLayout.VERTICAL
)
)
mRecyclerViewV.adapter = adapterFruit
}
else {
// Use special item decoration for large devices
val numberOfColumns = 2
mRecyclerViewV.layoutManager =
androidx.recyclerview.widget.GridLayoutManager(this, numberOfColumns)
val mListener = AdapterView.OnItemClickListener { _, _, _, _ -> }
adapterFruit = AdapterFruit(arrayItemsFruit, mListener)
mRecyclerViewV.adapter = adapterFruit
}
// Set Horizontal RecyclerView
mRecyclerViewH.layoutManager = LinearLayoutManager(this,
RecyclerView.HORIZONTAL,
false)
val mListener = AdapterView.OnItemClickListener { _, _, _, _ -> }
adapterAlphabet = AdapterAlphabet(arrayItemsBtns, mListener)
mRecyclerViewH.adapter = adapterAlphabet
}
}
Main layout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/ll_activityToolbarAndRecyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/my_toolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/mRecyclerViewWithToolbarV"
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/myToolbar"
app:layout_constraintBottom_toTopOf="#+id/mRecyclerViewWithToolbarH"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/mRecyclerViewWithToolbarH"
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/mRecyclerViewWithToolbarV"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Button layout
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.button.MaterialButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/myBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:gravity="start|center_vertical"
android:padding="20dp"
android:layout_margin="20dp"
android:textAllCaps="false"
android:textColor="?android:attr/textColorPrimary"
android:textSize="22sp"
app:strokeColor="?android:attr/textColorPrimary"
style="#style/Widget.MaterialComponents.Button.OutlinedButton" />
ItemAlphabet
data class ItemAlphabet(
val alphabetLetter: String
)
Alphabet index adapter
class AdapterAlphabet(
var listAlphabet: MutableList<ItemAlphabet>,
private val clickListener: AdapterView.OnItemClickListener
) : RecyclerView.Adapter<AdapterAlphabet.CompanyViewHolder>() {
class CompanyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var btnAlphabet: MaterialButton = itemView.findViewById(R.id.myBtn)
fun bind(alphabet: ItemAlphabet)
{
// Binding the data with the view holder views
btnAlphabet.text = alphabet.alphabetLetter
// Click events for list items (based on position)
itemView.setOnClickListener {v ->
// val intent: Intent = when (alphabet.alphabetLetter) {
// v.resources.getString(R.string.apple) -> {
//
// }
// else -> {
//// Intent
// }
// }
// itemView.context.startActivity(intent)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterAlphabet.CompanyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.rv_item_btn,parent,false)
return AdapterAlphabet.CompanyViewHolder(view)
}
override fun getItemCount(): Int {
return listAlphabet.size
}
override fun onBindViewHolder(holder: CompanyViewHolder, position: Int) {
// Getting the product of the specified position
val product = listAlphabet[position]
// Binding to click listener
holder.bind(product)
}
}
Tablet result
Update
Ali Ahsan's suggestion
Based on your screenshot in the updated question, I suggest the following layout structure:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/ll_activityToolbarAndRecyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/my_toolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/mRecyclerViewWithToolbarV"
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/myToolbar"
app:layout_constraintBottom_toTopOf="#+id/mRecyclerViewWithToolbarH"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/mRecyclerViewWithToolbarH"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:scrollbars="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Neither the toolbar nor the bottom RecyclerView specifies constraints against the middle RecyclerView. The effect should be to pin the toolbar to the top of the screen and the bottom RecyclerView to the bottom of the screen.
(Note that it's okay to use wrap_content for the height of the bottom RV, because the "scrolling"/"recycling" axis is horizontal.)
Then, you can have the middle RecyclerView constrain all four of its edges to the parent, the toolbar, and the bottom RecyclerView. This will cause the middle RV to "stretch" to fill all remaining space on the screen.

Collapsing Toolbar layout with logo, title, subtitle in toolbar

I want do this but with Collapsing toolbar layout or display the logo and title in toolbar after scroll.
<!-- Toolbars -->
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="#dimen/detail_backdrop_height"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp"
android:fitsSystemWindows="true">
<ImageView
android:id="#+id/background_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="#drawable/background_1"
app:layout_collapseMode="parallax"
android:fitsSystemWindows="true"/>
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<ImageView
android:id="#+id/avatar_image"
android:layout_width="#dimen/circular_image_avatar"
android:layout_height="#dimen/circular_image_avatar"
android:gravity="center"
android:scaleType="centerCrop"
android:src="#drawable/ic_placerholder"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:transitionName="image_toolbar"/>
<TextView
android:id="#+id/profile_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Name title"
android:textAlignment="center"
android:layout_marginTop="#dimen/item_padding_top_bottom"
android:gravity="center"
style="#style/titleText_toolbar"
android:layout_below="#+id/avatar_image"
android:transitionName="title_toolbar"/>
<TextView
android:id="#+id/profile_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Subtitle"
android:textAlignment="center"
android:gravity="center"
style="#style/captionText_toolbar"
android:layout_below="#+id/profile_title" />
</RelativeLayout>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_collapseMode="pin">
<!-- avatar image and title, subtitle -->
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
Please help me
I have preperead two amaizing avatar collapsing demo samples with approach that doesn’t use a custom CoordinatorLayoutBehavior!
To view my samples native code: "Collapsing Avatar Toolbar Sample"
To read my "Animation Collapsing Toolbar Android" post on Medium.
demo 1 demo 2
Instead of use use a custom CoordinatorLayoutBehavior i use an OnOffsetChangedListener which comes from AppBarLayout.
private lateinit var appBarLayout: AppBarLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_demo_1)
...
appBarLayout = findViewById(R.id.app_bar_layout)
/**/
appBarLayout.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { appBarLayout, i ->
...
/**/
updateViews(Math.abs(i / appBarLayout.totalScrollRange.toFloat()))
})
}
Demo 1
in updateViews method avatar changes the size and changes avatar’s X, Y position translation in first demo.
private fun updateViews(offset: Float) {
...
/* Collapse avatar img*/
ivUserAvatar.apply {
when {
offset > avatarAnimateStartPointY -> {
val avatarCollapseAnimateOffset = (offset - avatarAnimateStartPointY) * avatarCollapseAnimationChangeWeight
val avatarSize = EXPAND_AVATAR_SIZE - (EXPAND_AVATAR_SIZE - COLLAPSE_IMAGE_SIZE) * avatarCollapseAnimateOffset
this.layoutParams.also {
it.height = Math.round(avatarSize)
it.width = Math.round(avatarSize)
}
invisibleTextViewWorkAround.setTextSize(TypedValue.COMPLEX_UNIT_PX, offset)
this.translationX = ((appBarLayout.width - horizontalToolbarAvatarMargin - avatarSize) / 2) * avatarCollapseAnimateOffset
this.translationY = ((toolbar.height - verticalToolbarAvatarMargin - avatarSize ) / 2) * avatarCollapseAnimateOffset
}
else -> this.layoutParams.also {
if (it.height != EXPAND_AVATAR_SIZE.toInt()) {
it.height = EXPAND_AVATAR_SIZE.toInt()
it.width = EXPAND_AVATAR_SIZE.toInt()
this.layoutParams = it
}
translationX = 0f
}
}
}
}
to find avatarAnimateStartPointY and avatarCollapseAnimationChangeWeight (for convert general offset to avatar animate offset):
private var avatarAnimateStartPointY: Float = 0F
private var avatarCollapseAnimationChangeWeight: Float = 0F
private var isCalculated = false
private var verticalToolbarAvatarMargin =0F
...
if (isCalculated.not()) {
avatarAnimateStartPointY =
Math.abs((appBarLayout.height - (EXPAND_AVATAR_SIZE + horizontalToolbarAvatarMargin)) / appBarLayout.totalScrollRange)
avatarCollapseAnimationChangeWeight = 1 / (1 - avatarAnimateStartPointY)
verticalToolbarAvatarMargin = (toolbar.height - COLLAPSE_IMAGE_SIZE) * 2
isCalculated = true
}
Demo 2
avatar change his size and than animate move to right at one moment with top toolbar text became to show and moving to left.
You need to track states: TO_EXPANDED_STATE changing, TO_COLLAPSED_STATE changing, WAIT_FOR_SWITCH.
/*Collapsed/expended sizes for views*/
val result: Pair<Int, Int> = when {
percentOffset < ABROAD -> {
Pair(TO_EXPANDED_STATE, cashCollapseState?.second ?: WAIT_FOR_SWITCH)
}
else -> {
Pair(TO_COLLAPSED_STATE, cashCollapseState?.second ?: WAIT_FOR_SWITCH)
}
}
Create animation for avatar on state switch change:
result.apply {
var translationY = 0f
var headContainerHeight = 0f
val translationX: Float
var currentImageSize = 0
when {
cashCollapseState != null && cashCollapseState != this -> {
when (first) {
TO_EXPANDED_STATE -> {
translationY = toolbar.height.toFloat()
headContainerHeight = appBarLayout.totalScrollRange.toFloat()
currentImageSize = EXPAND_AVATAR_SIZE.toInt()
/**/
titleToolbarText.visibility = View.VISIBLE
titleToolbarTextSingle.visibility = View.INVISIBLE
background.setBackgroundColor(ContextCompat.getColor(this#Demo2Activity, R.color.color_transparent))
/**/
ivAvatar.translationX = 0f
}
TO_COLLAPSED_STATE -> {
background.setBackgroundColor(ContextCompat.getColor(this#Demo2Activity, R.color.colorPrimary))
currentImageSize = COLLAPSE_IMAGE_SIZE.toInt()
translationY = appBarLayout.totalScrollRange.toFloat() - (toolbar.height - COLLAPSE_IMAGE_SIZE) / 2
headContainerHeight = toolbar.height.toFloat()
translationX = appBarLayout.width / 2f - COLLAPSE_IMAGE_SIZE / 2 - margin * 2
/**/
ValueAnimator.ofFloat(ivAvatar.translationX, translationX).apply {
addUpdateListener {
if (cashCollapseState!!.first == TO_COLLAPSED_STATE) {
ivAvatar.translationX = it.animatedValue as Float
}
}
interpolator = AnticipateOvershootInterpolator()
startDelay = 69
duration = 350
start()
}
...
}
}
ivAvatar.apply {
layoutParams.height = currentImageSize
layoutParams.width = currentImageSize
}
collapsingAvatarContainer.apply {
layoutParams.height = headContainerHeight.toInt()
this.translationY = translationY
requestLayout()
}
/**/
cashCollapseState = Pair(first, SWITCHED)
}
To view my samples native code: "Collapsing Avatar Toolbar Sample"
I think these type of animations can be achieved easily using MotionLayout. I have implemented sample collapsing layout using MotionLayout here. You can modify it for your use case. Simple change the start and end constraints.

Categories

Resources