Skip to content

Instantly share code, notes, and snippets.

@AminMoradian
Last active August 31, 2020 00:41
Show Gist options
  • Save AminMoradian/8461c7fd0cf51615a369899e0c28d0b4 to your computer and use it in GitHub Desktop.
Save AminMoradian/8461c7fd0cf51615a369899e0c28d0b4 to your computer and use it in GitHub Desktop.
Android CollapsingToolbar Profile Avatar
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
android:background="?attr/backgroundColor"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/imageViewBlurAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"
tools:ignore="ContentDescription"
android:src="@drawable/photo_female_6_blur" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
<com.liam.amin.CollapsingAvatarToolbar
android:id="@+id/stuff_container"
android:layout_width="wrap_content"
android:layout_height="?attr/actionBarSize"
app:expandedImageSize="60dp"
app:expandedTextColor="@color/white"
app:collapsedTextColor="?attr/titleTextColor">
<com.github.abdularis.civ.AvatarImageView
android:id="@+id/cat_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
app:strokeColor="@color/white"
app:strokeWidth="2dp"
app:view_state="IMAGE"
android:src="@drawable/photo_female_6" />
<TextView
android:id="@+id/cat_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amin Moradian"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"/>
</com.liam.amin.CollapsingAvatarToolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<declare-styleable name="DrawableCompatImageView">
<attr name="drawableShape" />
<attr name="drawableTintColor" />
<attr name="srcImage" />
<attr name="srcImageTint" />
</declare-styleable>
package com.liam.amin
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import com.google.android.material.appbar.AppBarLayout
import kotlin.math.abs
class CollapsingAvatarToolbar(context: Context, attrs: AttributeSet?) :
LinearLayout(context, attrs), AppBarLayout.OnOffsetChangedListener {
private var avatarView: View? = null
private var titleView: TextView? = null
private var collapsedPadding: Float = 0f
private var expandedPadding: Float = 0f
private var expandedImageSize: Float = 0f
private var collapsedImageSize: Float = 0f
private var collapsedTextSize: Float = 0f
private var expandedTextSize: Float = 0f
@ColorInt
private var collapsedTextColor: Int = 0
@ColorInt
private var expandedTextColor: Int = 0
private var valuesCalculatedAlready = false
private var toolbar: Toolbar? = null
private var appBarLayout: AppBarLayout? = null
private var collapsedHeight: Float = 0.toFloat()
private var expandedHeight: Float = 0.toFloat()
private var maxOffset: Float = 0.toFloat()
constructor(context: Context) : this(context, null) {
init()
}
init {
init()
val a =
context.theme.obtainStyledAttributes(attrs, R.styleable.CollapsingAvatarToolbar, 0, 0)
try {
collapsedPadding =
a.getDimension(R.styleable.CollapsingAvatarToolbar_collapsedPadding, -1f)
expandedPadding =
a.getDimension(R.styleable.CollapsingAvatarToolbar_expandedPadding, -1f)
collapsedImageSize =
a.getDimension(R.styleable.CollapsingAvatarToolbar_collapsedImageSize, -1f)
expandedImageSize =
a.getDimension(R.styleable.CollapsingAvatarToolbar_expandedImageSize, -1f)
collapsedTextSize =
a.getDimension(R.styleable.CollapsingAvatarToolbar_collapsedTextSize, -1f)
expandedTextSize =
a.getDimension(R.styleable.CollapsingAvatarToolbar_expandedTextSize, -1f)
expandedTextColor =
a.getColor(
R.styleable.CollapsingAvatarToolbar_expandedTextColor,
ContextCompat.getColor(context, android.R.color.black)
)
collapsedTextColor =
a.getColor(
R.styleable.CollapsingAvatarToolbar_collapsedTextColor,
ContextCompat.getColor(context, android.R.color.black)
)
} finally {
a.recycle()
}
val resources = resources
if (collapsedPadding < 0) {
collapsedPadding = resources.getDimension(R.dimen.default_collapsed_padding)
}
if (expandedPadding < 0) {
expandedPadding = resources.getDimension(R.dimen.default_expanded_padding)
}
if (collapsedImageSize < 0) {
collapsedImageSize = resources.getDimension(R.dimen.default_collapsed_image_size)
}
if (expandedImageSize < 0) {
expandedImageSize = resources.getDimension(R.dimen.default_expanded_image_size)
}
if (collapsedTextSize < 0) {
collapsedTextSize = resources.getDimension(R.dimen.default_collapsed_text_size)
}
if (expandedTextSize < 0) {
expandedTextSize = resources.getDimension(R.dimen.default_expanded_text_size)
}
}
private fun init() {
orientation = HORIZONTAL
}
private fun findParentAppBarLayout(): AppBarLayout {
val parent = this.parent
return parent as? AppBarLayout ?: if (parent.parent is AppBarLayout) {
parent.parent as AppBarLayout
} else {
throw IllegalStateException("Must be inside an AppBarLayout") //TODO actually, a collapsingtoolbar
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
findViews()
if (!isInEditMode) {
appBarLayout!!.addOnOffsetChangedListener(this)
} else {
setExpandedValuesForEditMode()
}
}
private fun setExpandedValuesForEditMode() {
calculateValues()
updateViews(1f, 0)
}
private fun findViews() {
appBarLayout = findParentAppBarLayout()
toolbar = findSiblingToolbar()
avatarView = findAvatar()
titleView = findTitle()
}
private fun findAvatar(): View {
return findViewById(R.id.cat_avatar)
?: throw IllegalStateException("View with id ta_avatar not found")
}
private fun findTitle(): TextView {
return findViewById<View>(R.id.cat_title) as TextView
}
private fun findSiblingToolbar(): Toolbar {
val parent = this.parent as ViewGroup
var i = 0
val c = parent.childCount
while (i < c) {
val child = parent.getChildAt(i)
if (child is Toolbar) {
return child
}
i++
}
throw IllegalStateException("No toolbar found as sibling")
}
override fun onOffsetChanged(appBarLayout: AppBarLayout, offset: Int) {
if (!valuesCalculatedAlready) {
calculateValues()
valuesCalculatedAlready = true
}
val expandedPercentage = 1 - -offset / maxOffset
updateViews(expandedPercentage, offset)
when {
offset == 0 ->
titleView?.setTextColor(ColorStateList.valueOf(expandedTextColor))
abs(offset) >= appBarLayout.totalScrollRange ->
titleView?.setTextColor(collapsedTextColor)
else -> {
if (offset <= -292) titleView?.setTextColor(ColorStateList.valueOf(collapsedTextColor))
else titleView?.setTextColor(ColorStateList.valueOf(expandedTextColor))
}
}
}
private fun calculateValues() {
collapsedHeight = toolbar!!.height.toFloat()
expandedHeight = (appBarLayout!!.height - toolbar!!.height).toFloat()
maxOffset = expandedHeight
}
private fun updateViews(expandedPercentage: Float, currentOffset: Int) {
val inversePercentage = 1 - expandedPercentage
val translation = -currentOffset + toolbar!!.height.toFloat() * expandedPercentage
val currHeight = collapsedHeight + (expandedHeight - collapsedHeight) * expandedPercentage
val currentPadding =
expandedPadding + (collapsedPadding - expandedPadding) * inversePercentage
val currentImageSize =
collapsedImageSize + (expandedImageSize - collapsedImageSize) * expandedPercentage
val currentTextSize =
collapsedTextSize + (expandedTextSize - collapsedTextSize) * expandedPercentage
setContainerOffset(translation)
setContainerHeight(currHeight.toInt())
setPadding(currentPadding.toInt())
setAvatarSize(currentImageSize.toInt())
setTextSize(currentTextSize)
}
private fun setContainerOffset(translation: Float) {
this.translationY = translation
}
private fun setContainerHeight(currHeight: Int) {
this.layoutParams.height = currHeight
}
private fun setPadding(currentPadding: Int) {
this.setPadding(currentPadding, 0, 0, 0)
}
private fun setTextSize(currentTextSize: Float) {
titleView!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, currentTextSize)
}
private fun setAvatarSize(currentImageSize: Int) {
avatarView!!.layoutParams.height = currentImageSize
avatarView!!.layoutParams.width = currentImageSize
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="default_message_padding">8dp</dimen>
<dimen name="space_between_time_and_state">2dp</dimen>
<dimen name="space_between_message_and_footer">8dp</dimen>
<dimen name="message_margin">40dp</dimen>
<dimen name="message_item_content_padding">2dp</dimen>
<dimen name="recyclerview_content_padding">8dp</dimen>
<dimen name="messages_top_margin">8dp</dimen>
</resources>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment