Timeline : add badge on jump to bottom view

This commit is contained in:
ganfra 2019-09-19 19:12:45 +02:00
parent ea0809ff87
commit d1ff3314a7
4 changed files with 205 additions and 1 deletions

View File

@ -0,0 +1,186 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.core.platform
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Paint.ANTI_ALIAS_FLAG
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.text.TextPaint
import android.util.AttributeSet
import androidx.core.content.res.use
import com.google.android.material.floatingactionbutton.FloatingActionButton
import im.vector.riotx.R
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sin
class BadgeFloatingActionButton @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FloatingActionButton(context, attrs, defStyleAttr) {
private val textPaint = TextPaint(ANTI_ALIAS_FLAG).apply {
textAlign = Paint.Align.LEFT
}
private val tintPaint = Paint(ANTI_ALIAS_FLAG)
private var countStr: String
private var countMaxStr: String
private var counterBounds: RectF = RectF()
private var counterTextBounds: Rect = Rect()
private var counterMaxTextBounds: Rect = Rect()
private var counterPossibleCenter: PointF = PointF()
private var fabBounds: Rect = Rect()
var counterTextColor: Int
get() = textPaint.color
set(value) {
val was = textPaint.color
if (was != value) {
textPaint.color = value
invalidate()
}
}
var counterBackgroundColor: Int
get() = tintPaint.color
set(value) {
val was = tintPaint.color
if (was != value) {
tintPaint.color = value
invalidate()
}
}
var counterTextSize: Float
get() = textPaint.textSize
set(value) {
val was = textPaint.textSize
if (was != value) {
textPaint.textSize = value
invalidate()
requestLayout()
}
}
var counterTextPadding: Float = 0f
set(value) {
if (field != value) {
field = value
invalidate()
requestLayout()
}
}
var maxCount: Int = 99
set(value) {
if (field != value) {
field = value
countMaxStr = "$value+"
requestLayout()
}
}
var count: Int = 0
set(value) {
if (field != value) {
field = value
countStr = countStr(value)
textPaint.getTextBounds(countStr, 0, countStr.length, counterTextBounds)
invalidate()
}
}
init {
countStr = countStr(count)
textPaint.getTextBounds(countStr, 0, countStr.length, counterTextBounds)
countMaxStr = "$maxCount+"
attrs?.let { initAttrs(attrs) }
}
@SuppressWarnings("ResourceType", "Recycle")
private fun initAttrs(attrs: AttributeSet) {
context.obtainStyledAttributes(attrs, R.styleable.BadgeFloatingActionButton).use {
counterBackgroundColor = it.getColor(R.styleable.BadgeFloatingActionButton_badgeBackgroundColor, 0)
counterTextPadding = it.getDimension(R.styleable.BadgeFloatingActionButton_badgeTextPadding, 0f)
counterTextSize = it.getDimension(R.styleable.BadgeFloatingActionButton_badgeTextSize, 14f)
counterTextColor = it.getColor(R.styleable.BadgeFloatingActionButton_badgeTextColor, Color.WHITE)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
calculateCounterBounds(counterBounds)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (count > 0) {
canvas.drawCircle(counterBounds.centerX(), counterBounds.centerY(), counterBounds.width() / 2f, tintPaint)
val textX = counterBounds.centerX() - counterTextBounds.width() / 2f - counterTextBounds.left
val textY = counterBounds.centerY() + counterTextBounds.height() / 2f - counterTextBounds.bottom
canvas.drawText(countStr, textX, textY, textPaint)
}
}
private fun calculateCounterBounds(outRect: RectF) {
getMeasuredContentRect(fabBounds)
calculateCounterCenter(fabBounds, counterPossibleCenter)
textPaint.getTextBounds(countMaxStr, 0, countMaxStr.length, counterMaxTextBounds)
val counterDiameter = max(counterMaxTextBounds.width(), counterMaxTextBounds.height()) + 2 * counterTextPadding
val counterRight = min(counterPossibleCenter.x + counterDiameter / 2, fabBounds.right.toFloat())
val counterTop = max(counterPossibleCenter.y - counterDiameter / 2, fabBounds.top.toFloat())
outRect.set(counterRight - counterDiameter, counterTop, counterRight, counterTop + counterDiameter)
}
private fun calculateCounterCenter(inBounds: Rect, outPoint: PointF) {
val radius = min(inBounds.width(), inBounds.height()) / 2f
calculateCounterCenter(radius, outPoint)
outPoint.x = inBounds.centerX() + outPoint.x
outPoint.y = inBounds.centerY() - outPoint.y
}
private fun calculateCounterCenter(radius: Float, outPoint: PointF) =
calculateCounterCenter(radius, (PI / 4).toFloat(), outPoint)
private fun calculateCounterCenter(radius: Float, angle: Float, outPoint: PointF) {
outPoint.x = radius * cos(angle)
outPoint.y = radius * sin(angle)
}
private fun countStr(count: Int) = if (count > maxCount) "$maxCount+" else count.toString()
companion object {
val TEXT_APPEARANCE_SUPPORTED_ATTRS = intArrayOf(android.R.attr.textSize, android.R.attr.textColor)
}
}

View File

@ -739,6 +739,8 @@ class RoomDetailFragment :
avatarRenderer.render(it, roomToolbarAvatarImageView)
roomToolbarSubtitleView.setTextOrHide(it.topic)
}
jumpToBottomView.count = it.notificationCount
}
}

View File

@ -161,13 +161,17 @@
app:layout_constraintTop_toBottomOf="@+id/roomToolbar"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
<im.vector.riotx.core.platform.BadgeFloatingActionButton
android:id="@+id/jumpToBottomView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/chevron_down"
app:backgroundTint="#FFFFFF"
app:badgeBackgroundColor="@color/riotx_accent"
app:badgeTextColor="@color/white"
app:badgeTextPadding="2dp"
app:badgeTextSize="10sp"
app:layout_constraintBottom_toTopOf="@id/composerLayout"
app:layout_constraintEnd_toEndOf="parent"
app:maxImageSize="16dp"

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BadgeFloatingActionButton">
<attr name="badgeBackgroundColor" format="color" />
<attr name="badgeTextColor" format="color" />
<attr name="badgeTextPadding" format="dimension" />
<attr name="badgeTextSize" format="dimension" />
</declare-styleable>
</resources>