Timeline : add badge on jump to bottom view
This commit is contained in:
parent
ea0809ff87
commit
d1ff3314a7
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -739,6 +739,8 @@ class RoomDetailFragment :
|
|||
avatarRenderer.render(it, roomToolbarAvatarImageView)
|
||||
roomToolbarSubtitleView.setTextOrHide(it.topic)
|
||||
}
|
||||
|
||||
jumpToBottomView.count = it.notificationCount
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue