Last active
August 31, 2020 00:41
-
-
Save AminMoradian/8461c7fd0cf51615a369899e0c28d0b4 to your computer and use it in GitHub Desktop.
Android CollapsingToolbar Profile Avatar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<declare-styleable name="DrawableCompatImageView"> | |
<attr name="drawableShape" /> | |
<attr name="drawableTintColor" /> | |
<attr name="srcImage" /> | |
<attr name="srcImageTint" /> | |
</declare-styleable> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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