Manga description adjustments (#6011)

* Manga description adjustments

- Animated state changes
- Adjust scrim position to fully show 2 lines when shrunk
- Set minLines to avoid scrim hiding oneliner

* Change icon and adjust animation

* Revert fancy scrim animation
This commit is contained in:
Ivan Iskandar 2021-10-09 22:02:45 +07:00 committed by GitHub
parent 2d1404d155
commit f32f1eeaa5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 480 additions and 344 deletions

View File

@ -20,9 +20,7 @@ import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.view.loadAnyAutoPause
import eu.kanade.tachiyomi.util.view.setChips
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.android.view.longClicks
@ -45,13 +43,14 @@ class MangaInfoHeaderAdapter(
private lateinit var binding: MangaInfoHeaderBinding
private var initialLoad: Boolean = true
private val maxLines = 3
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
updateCoverPosition()
// Expand manga info if navigated from source listing or explicitly set to
// (e.g. on tablets)
binding.mangaSummarySection.expanded = fromSource || isTablet
return HeaderViewHolder(binding.root)
}
@ -180,15 +179,6 @@ class MangaInfoHeaderAdapter(
}
.launchIn(controller.viewScope)
binding.mangaSummaryText.longClicks()
.onEach {
controller.activity?.copyToClipboard(
view.context.getString(R.string.description),
binding.mangaSummaryText.text.toString()
)
}
.launchIn(controller.viewScope)
binding.mangaCover.clicks()
.onEach {
controller.showFullCoverDialog()
@ -201,7 +191,7 @@ class MangaInfoHeaderAdapter(
}
.launchIn(controller.viewScope)
setMangaInfo(manga, source)
setMangaInfo()
}
private fun showCoverOptionsDialog() {
@ -231,7 +221,7 @@ class MangaInfoHeaderAdapter(
* @param manga manga object containing information about manga.
* @param source the source of the manga.
*/
private fun setMangaInfo(manga: Manga, source: Source?) {
private fun setMangaInfo() {
// Update full title TextView.
binding.mangaFullTitle.text = if (manga.title.isBlank()) {
view.context.getString(R.string.unknown)
@ -254,27 +244,23 @@ class MangaInfoHeaderAdapter(
}
// If manga source is known update source TextView.
val mangaSource = source?.toString()
val mangaSource = source.toString()
with(binding.mangaSource) {
if (mangaSource != null) {
val enabledLanguages = preferences.enabledLanguages().get()
.filterNot { it in listOf("all", "other") }
val enabledLanguages = preferences.enabledLanguages().get()
.filterNot { it in listOf("all", "other") }
val hasOneActiveLanguages = enabledLanguages.size == 1
val isInEnabledLanguages = source.lang in enabledLanguages
text = when {
// For edge cases where user disables a source they got manga of in their library.
hasOneActiveLanguages && !isInEnabledLanguages -> mangaSource
// Hide the language tag when only one language is used.
hasOneActiveLanguages && isInEnabledLanguages -> source.name
else -> mangaSource
}
val hasOneActiveLanguages = enabledLanguages.size == 1
val isInEnabledLanguages = source.lang in enabledLanguages
text = when {
// For edge cases where user disables a source they got manga of in their library.
hasOneActiveLanguages && !isInEnabledLanguages -> mangaSource
// Hide the language tag when only one language is used.
hasOneActiveLanguages && isInEnabledLanguages -> source.name
else -> mangaSource
}
setOnClickListener {
controller.performSearch(sourceManager.getOrStub(source.id).name)
}
} else {
text = view.context.getString(R.string.unknown)
setOnClickListener {
controller.performSearch(sourceManager.getOrStub(source.id).name)
}
}
@ -296,84 +282,9 @@ class MangaInfoHeaderAdapter(
binding.mangaCover.loadAnyAutoPause(manga)
// Manga info section
val hasInfoContent = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
showMangaInfo(hasInfoContent)
if (hasInfoContent) {
// Update description TextView.
binding.mangaSummaryText.text = updateDescription(manga.description, (fromSource || isTablet).not())
// Update genres list
if (!manga.genre.isNullOrBlank()) {
binding.mangaGenresTagsCompactChips.setChips(
manga.getGenres(),
controller::performGenreSearch
)
binding.mangaGenresTagsFullChips.setChips(
manga.getGenres(),
controller::performGenreSearch
)
} else {
binding.mangaGenresTagsCompact.isVisible = false
binding.mangaGenresTagsCompactChips.isVisible = false
binding.mangaGenresTagsFullChips.isVisible = false
}
// Handle showing more or less info
merge(
binding.mangaSummaryText.clicks(),
binding.mangaInfoToggleMore.clicks(),
binding.mangaInfoToggleLess.clicks(),
binding.mangaSummarySection.clicks(),
)
.onEach { toggleMangaInfo() }
.launchIn(controller.viewScope)
if (initialLoad) {
binding.mangaGenresTagsCompact.requestLayout()
}
// Expand manga info if navigated from source listing or explicitly set to
// (e.g. on tablets)
if (initialLoad && (fromSource || isTablet)) {
toggleMangaInfo()
initialLoad = false
}
}
}
private fun showMangaInfo(visible: Boolean) {
binding.mangaSummarySection.isVisible = visible
}
private fun toggleMangaInfo() {
val isCurrentlyExpanded = binding.mangaSummaryText.maxLines != maxLines
binding.mangaInfoToggleMore.isVisible = isCurrentlyExpanded
binding.mangaInfoScrim.isVisible = isCurrentlyExpanded
binding.mangaInfoToggleMoreScrim.isVisible = isCurrentlyExpanded
binding.mangaGenresTagsCompact.isVisible = isCurrentlyExpanded
binding.mangaGenresTagsCompactChips.isVisible = isCurrentlyExpanded
binding.mangaInfoToggleLess.isVisible = !isCurrentlyExpanded
binding.mangaGenresTagsFullChips.isVisible = !isCurrentlyExpanded
binding.mangaSummaryText.text = updateDescription(manga.description, isCurrentlyExpanded)
binding.mangaSummaryText.maxLines = when {
isCurrentlyExpanded -> maxLines
else -> Int.MAX_VALUE
}
}
private fun updateDescription(description: String?, isCurrentlyExpanded: Boolean): CharSequence {
return when {
description.isNullOrBlank() -> view.context.getString(R.string.unknown)
isCurrentlyExpanded ->
description
.replace(Regex(" +\$", setOf(RegexOption.MULTILINE)), "")
.replace(Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE)), "\n")
else -> description
}
binding.mangaSummarySection.isVisible = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
binding.mangaSummarySection.description = manga.description
binding.mangaSummarySection.setTags(manga.getGenres(), controller::performGenreSearch)
}
/**

View File

@ -0,0 +1,188 @@
package eu.kanade.tachiyomi.widget
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.drawable.Animatable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.annotation.AttrRes
import androidx.annotation.StyleRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.view.doOnNextLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.MangaSummaryBinding
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.view.setChips
import kotlin.math.roundToInt
import kotlin.math.roundToLong
class MangaSummaryView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0,
@StyleRes defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
private val binding = MangaSummaryBinding.inflate(LayoutInflater.from(context), this, true)
private var animatorSet: AnimatorSet? = null
private var recalculateHeights = false
private var descExpandedHeight = -1
private var descShrunkHeight = -1
var expanded = false
set(value) {
if (field != value) {
field = value
updateExpandState()
}
}
var description: CharSequence? = null
set(value) {
if (field != value) {
field = if (value.isNullOrBlank()) {
context.getString(R.string.unknown)
} else {
value
}
binding.descriptionText.text = field
recalculateHeights = true
doOnNextLayout {
updateExpandState()
}
requestLayout()
}
}
fun setTags(items: List<String>?, onClick: (item: String) -> Unit) {
binding.tagChipsShrunk.setChips(items, onClick)
binding.tagChipsExpanded.setChips(items, onClick)
}
private fun updateExpandState() = binding.apply {
val initialSetup = descriptionText.maxHeight < 0
val maxHeightTarget = if (expanded) descExpandedHeight else descShrunkHeight
val maxHeightStart = if (initialSetup) maxHeightTarget else descriptionText.maxHeight
val descMaxHeightAnimator = ValueAnimator().apply {
setIntValues(maxHeightStart, maxHeightTarget)
addUpdateListener {
descriptionText.maxHeight = it.animatedValue as Int
}
}
val toggleDrawable = ContextCompat.getDrawable(
context,
if (expanded) R.drawable.anim_caret_up else R.drawable.anim_caret_down
)
toggleMore.setImageDrawable(toggleDrawable)
var pastHalf = false
val toggleTarget = if (expanded) 1F else 0F
val toggleStart = if (initialSetup) {
toggleTarget
} else {
toggleMore.translationY / toggleMore.height
}
val toggleAnimator = ValueAnimator().apply {
setFloatValues(toggleStart, toggleTarget)
addUpdateListener {
val value = it.animatedValue as Float
toggleMore.translationY = toggleMore.height * value
descriptionScrim.translationY = toggleMore.translationY
toggleMoreScrim.translationY = toggleMore.translationY
tagChipsShrunkContainer.updateLayoutParams<ConstraintLayout.LayoutParams> {
topMargin = toggleMore.translationY.roundToInt()
}
tagChipsExpanded.updateLayoutParams<ConstraintLayout.LayoutParams> {
topMargin = toggleMore.translationY.roundToInt()
}
// Update non-animatable objects mid-animation makes it feel less abrupt
if (it.animatedFraction >= 0.5F && !pastHalf) {
pastHalf = true
descriptionText.text = trimWhenNeeded(description)
tagChipsShrunkContainer.scrollX = 0
tagChipsShrunkContainer.isVisible = !expanded
tagChipsExpanded.isVisible = expanded
}
}
}
animatorSet?.cancel()
animatorSet = AnimatorSet().apply {
interpolator = FastOutSlowInInterpolator()
duration = (TOGGLE_ANIM_DURATION * context.animatorDurationScale).roundToLong()
playTogether(toggleAnimator, descMaxHeightAnimator)
start()
}
(toggleDrawable as? Animatable)?.start()
}
private fun trimWhenNeeded(text: CharSequence?): CharSequence? {
return if (!expanded) {
text
?.replace(Regex(" +\$", setOf(RegexOption.MULTILINE)), "")
?.replace(Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE)), "\n")
} else {
text
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (!recalculateHeights) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
recalculateHeights = false
// Measure with expanded lines
binding.descriptionText.maxLines = Int.MAX_VALUE
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
descExpandedHeight = binding.descriptionText.measuredHeight
// Measure with shrunk lines
binding.descriptionText.maxLines = SHRUNK_DESC_MAX_LINES
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
descShrunkHeight = binding.descriptionText.measuredHeight
}
init {
binding.descriptionText.apply {
// So that 1 line of text won't be hidden by scrim
minLines = DESC_MIN_LINES
setOnLongClickListener {
context.copyToClipboard(
context.getString(R.string.description),
text.toString()
)
true
}
}
arrayOf(
binding.descriptionText,
binding.descriptionScrim,
binding.toggleMoreScrim,
binding.toggleMore
).forEach {
it.setOnClickListener { expanded = !expanded }
}
}
}
private const val TOGGLE_ANIM_DURATION = 300L
private const val DESC_MIN_LINES = 2
private const val SHRUNK_DESC_MAX_LINES = 3

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="caret_up"
android:width="24.0dip"
android:height="24.0dip"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<group
android:name="caret01"
android:rotation="90.0"
android:translateX="12.0"
android:translateY="15.0">
<group
android:name="caret_l"
android:rotation="45.0">
<group
android:name="caret_l_pivot"
android:translateY="4.0">
<group
android:name="caret_l_rect_position"
android:translateY="-1.0">
<path
android:name="caret_l_rect"
android:fillColor="@android:color/black"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
<group
android:name="caret_r"
android:rotation="-45.0">
<group
android:name="caret_r_pivot"
android:translateY="-4.0">
<group
android:name="caret_r_rect_position"
android:translateY="1.0">
<path
android:name="caret_r_rect"
android:fillColor="@android:color/black"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="caret01">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:pathData="M 12.0,9.0 c 0.0,0.66667 0.0,5.0 0.0,6.0"
android:propertyXName="translateX"
android:propertyYName="translateY" />
</aapt:attr>
</target>
<target android:name="caret_l">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:valueFrom="-45.0"
android:valueTo="45.0"
android:valueType="floatType" />
</aapt:attr>
</target>
<target
android:name="caret_r">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:valueFrom="45.0"
android:valueTo="-45.0"
android:valueType="floatType" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="caret_up"
android:height="24.0dip"
android:width="24.0dip"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<group
android:name="caret02"
android:rotation="90.0"
android:translateX="12.0"
android:translateY="9.0">
<group
android:name="caret02_l"
android:rotation="-45.0">
<group
android:name="caret02_l_pivot"
android:translateY="4.0">
<group
android:name="caret02_l_rect_position"
android:translateY="-1.0">
<path
android:name="caret02_l_rect"
android:fillColor="@android:color/black"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
<group
android:name="caret02_r"
android:rotation="45.0">
<group
android:name="caret02_r_pivot"
android:translateY="-4.0">
<group
android:name="caret02_r_rect_position"
android:translateY="1.0">
<path
android:name="caret02_r_rect"
android:fillColor="@android:color/black"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="caret02">
<aapt:attr name="android:animation">
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="300"
android:pathData="M 12.0,15.0 c 0.0,-1.0 0.0,-5.33333 0.0,-6.0"
android:propertyXName="translateX"
android:propertyYName="translateY" />
</aapt:attr>
</target>
<target android:name="caret02_l">
<aapt:attr name="android:animation">
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="300"
android:valueFrom="45.0"
android:valueTo="-45.0"
android:valueType="floatType"
android:propertyName="rotation" />
</aapt:attr>
</target>
<target android:name="caret02_r">
<aapt:attr name="android:animation">
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="300"
android:valueFrom="-45.0"
android:valueTo="45.0"
android:valueType="floatType"
android:propertyName="rotation" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -188,126 +188,12 @@
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
<eu.kanade.tachiyomi.widget.MangaSummaryView
android:id="@+id/manga_summary_section"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_actions">
<TextView
android:id="@+id/manga_summary_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
android:textIsSelectable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content" />
<View
android:id="@+id/manga_info_scrim"
android:layout_width="0dp"
android:layout_height="32sp"
android:background="@drawable/manga_info_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/manga_summary_text"
app:layout_constraintEnd_toEndOf="@+id/manga_summary_text"
app:layout_constraintStart_toStartOf="@+id/manga_summary_text" />
<View
android:id="@+id/manga_info_toggle_more_scrim"
android:layout_width="36sp"
android:layout_height="18sp"
android:background="@drawable/manga_info_more_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/manga_info_toggle_more"
app:layout_constraintEnd_toEndOf="@+id/manga_info_toggle_more"
app:layout_constraintStart_toStartOf="@+id/manga_info_toggle_more"
app:layout_constraintTop_toTopOf="@+id/manga_info_toggle_more" />
<ImageButton
android:id="@+id/manga_info_toggle_more"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="-4dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_expand"
android:src="@drawable/ic_expand_more_24dp"
app:layout_constraintBottom_toBottomOf="@id/manga_summary_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tint="?android:attr/textColorPrimary" />
<ImageButton
android:id="@+id/manga_info_toggle_less"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_collapse"
android:src="@drawable/ic_expand_less_24dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_summary_text"
app:tint="?android:attr/textColorPrimary"
tools:visibility="visible" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_more">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_less"
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
app:layout_constraintTop_toBottomOf="@id/manga_actions" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -200,124 +200,12 @@
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
<eu.kanade.tachiyomi.widget.MangaSummaryView
android:id="@+id/manga_summary_section"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_actions">
<TextView
android:id="@+id/manga_summary_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
android:textIsSelectable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content" />
<View
android:id="@+id/manga_info_scrim"
android:layout_width="0dp"
android:layout_height="32sp"
android:background="@drawable/manga_info_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/manga_summary_text"
app:layout_constraintEnd_toEndOf="@+id/manga_summary_text"
app:layout_constraintStart_toStartOf="@+id/manga_summary_text" />
<View
android:id="@+id/manga_info_toggle_more_scrim"
android:layout_width="36sp"
android:layout_height="18sp"
android:background="@drawable/manga_info_more_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/manga_info_toggle_more"
app:layout_constraintEnd_toEndOf="@+id/manga_info_toggle_more"
app:layout_constraintStart_toStartOf="@+id/manga_info_toggle_more"
app:layout_constraintTop_toTopOf="@+id/manga_info_toggle_more" />
<ImageButton
android:id="@+id/manga_info_toggle_more"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="-4dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_expand"
android:src="@drawable/ic_expand_more_24dp"
app:layout_constraintBottom_toBottomOf="@id/manga_summary_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tint="?android:attr/textColorPrimary" />
<ImageButton
android:id="@+id/manga_info_toggle_less"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_collapse"
android:src="@drawable/ic_expand_less_24dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_summary_text"
app:tint="?android:attr/textColorPrimary"
tools:visibility="visible" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_more">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_less"
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
app:layout_constraintTop_toBottomOf="@id/manga_actions" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,94 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/description_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:ellipsize="end"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
android:textIsSelectable="false"
app:firstBaselineToTopHeight="0dp"
app:lastBaselineToBottomHeight="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content" />
<View
android:id="@+id/description_scrim"
android:layout_width="0dp"
android:layout_height="24sp"
android:background="@drawable/manga_info_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/description_text"
app:layout_constraintEnd_toEndOf="@+id/description_text"
app:layout_constraintStart_toStartOf="@+id/description_text" />
<View
android:id="@+id/toggle_more_scrim"
android:layout_width="36sp"
android:layout_height="18sp"
android:background="@drawable/manga_info_more_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/toggle_more"
app:layout_constraintEnd_toEndOf="@+id/toggle_more"
app:layout_constraintStart_toStartOf="@+id/toggle_more"
app:layout_constraintTop_toTopOf="@+id/toggle_more" />
<ImageButton
android:id="@+id/toggle_more"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="-6dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_expand"
android:padding="0dp"
android:src="@drawable/anim_caret_down"
app:layout_constraintBottom_toBottomOf="@id/description_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tint="?android:attr/textColorPrimary" />
<HorizontalScrollView
android:id="@+id/tag_chips_shrunk_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toggle_more">
<com.google.android.material.chip.ChipGroup
android:id="@+id/tag_chips_shrunk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
<com.google.android.material.chip.ChipGroup
android:id="@+id/tag_chips_expanded"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toggle_more"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>