Skip to content

Commit fc0cf82

Browse files
committed
Improve the transition api of the placeholders
1 parent 7e31eab commit fc0cf82

File tree

2 files changed

+105
-49
lines changed

2 files changed

+105
-49
lines changed

media-placeholders/src/main/java/org/wordpress/aztec/placeholders/ImageWithCaptionAdapter.kt

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
package org.wordpress.aztec.placeholders
22

33
import android.content.Context
4+
import android.util.Log
45
import android.view.Gravity
56
import android.view.View
7+
import android.view.animation.Animation
8+
import android.view.animation.Transformation
69
import android.widget.ImageView
710
import android.widget.LinearLayout
811
import android.widget.TextView
12+
import androidx.core.view.updateLayoutParams
913
import com.bumptech.glide.Glide
14+
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
15+
import kotlinx.coroutines.CoroutineScope
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.flow.StateFlow
18+
import kotlinx.coroutines.flow.map
19+
import kotlinx.coroutines.flow.stateIn
20+
import kotlinx.coroutines.launch
1021
import org.wordpress.aztec.AztecAttributes
1122
import org.wordpress.aztec.placeholders.PlaceholderManager.PlaceholderAdapter.Proportion
1223

@@ -17,15 +28,21 @@ import org.wordpress.aztec.placeholders.PlaceholderManager.PlaceholderAdapter.Pr
1728
class ImageWithCaptionAdapter(
1829
override val type: String = "image_with_caption"
1930
) : PlaceholderManager.PlaceholderAdapter {
20-
private val media = mutableMapOf<String, ImageWithCaptionObject>()
31+
private val media = mutableMapOf<String, StateFlow<ImageWithCaptionObject>>()
32+
private val scope = CoroutineScope(Dispatchers.Main)
2133
suspend override fun getHeight(attrs: AztecAttributes): Proportion {
2234
return Proportion.Ratio(attrs.getValue(HEIGHT).toFloatOrNull() ?: 0.5f)
2335
}
2436

25-
suspend override fun createView(context: Context, placeholderUuid: String, attrs: AztecAttributes): View {
26-
val imageWithCaptionObject = ImageWithCaptionObject(placeholderUuid, attrs.getValue(SRC_ATTRIBUTE), View.generateViewId()).apply {
27-
media[placeholderUuid] = this
28-
}
37+
suspend override fun createView(context: Context, placeholderUuid: String, viewParamsUpdate: StateFlow<PlaceholderManager.Placeholder.ViewParams>): View {
38+
val attrs = viewParamsUpdate.value.attrs
39+
val imageViewId = View.generateViewId()
40+
val stateFlow = viewParamsUpdate.map {
41+
ImageWithCaptionObject(placeholderUuid, it.attrs.getValue(SRC_ATTRIBUTE), imageViewId, it.width, it.height, it.initial)
42+
}.stateIn(scope)
43+
media[placeholderUuid] = stateFlow
44+
val imageWithCaptionObject = stateFlow.value
45+
Log.d("vojta2", "Drawing image with caption ${imageWithCaptionObject.src}")
2946
val captionLayoutId = View.generateViewId()
3047
val imageLayoutId = imageWithCaptionObject.layoutId
3148
val linearLayout = LinearLayout(context)
@@ -58,18 +75,52 @@ class ImageWithCaptionAdapter(
5875
return linearLayout
5976
}
6077

78+
override fun animateLayoutChanges(): Boolean {
79+
return true
80+
}
81+
6182
suspend override fun onViewCreated(view: View, placeholderUuid: String) {
6283
val image = media[placeholderUuid]!!
63-
val imageView = view.findViewById<ImageView>(image.layoutId)
64-
Glide.with(view).load(image.src).into(imageView)
84+
scope.launch {
85+
image.collect {
86+
val imageView = view.findViewById<ImageView>(it.layoutId)
87+
ResizeAnimation(view, it.width, it.height).apply {
88+
duration = if (it.initialLoad) {
89+
0
90+
} else {
91+
200
92+
}
93+
view.startAnimation(this)
94+
}
95+
Glide.with(view).load(it.src).transition(DrawableTransitionOptions.withCrossFade()).into(imageView)
96+
}
97+
}
6598
super.onViewCreated(view, placeholderUuid)
6699
}
67100

101+
class ResizeAnimation(private val view: View, private val newWidth: Int, private val newHeight: Int) : Animation() {
102+
private val startWidth: Int = view.width
103+
private val startHeight: Int = view.height
104+
105+
override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
106+
view.updateLayoutParams {
107+
width = startWidth + ((newWidth - startWidth) * interpolatedTime).toInt()
108+
height = startHeight + ((newHeight - startHeight) * interpolatedTime).toInt()
109+
}
110+
Log.d("vojta", "Changing height to ${view.layoutParams.height} and width to ${view.layoutParams.width}")
111+
view.requestLayout()
112+
}
113+
114+
override fun willChangeBounds(): Boolean {
115+
return true
116+
}
117+
}
118+
68119
override fun onPlaceholderDeleted(placeholderUuid: String) {
69120
media.remove(placeholderUuid)
70121
}
71122

72-
data class ImageWithCaptionObject(val id: String, val src: String, val layoutId: Int)
123+
data class ImageWithCaptionObject(val id: String, val src: String, val layoutId: Int, val width: Int, val height: Int, val initialLoad: Boolean)
73124

74125
companion object {
75126
private const val ADAPTER_TYPE = "image_with_caption"

media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ import android.graphics.drawable.Drawable
77
import android.text.Editable
88
import android.text.Layout
99
import android.text.Spanned
10-
import android.transition.TransitionManager
1110
import android.util.Log
1211
import android.view.MotionEvent
1312
import android.view.View
1413
import android.view.ViewTreeObserver
1514
import android.widget.FrameLayout
1615
import androidx.core.content.ContextCompat
16+
import androidx.core.view.updateLayoutParams
1717
import kotlinx.coroutines.CoroutineScope
1818
import kotlinx.coroutines.Dispatchers
1919
import kotlinx.coroutines.Job
2020
import kotlinx.coroutines.delay
21+
import kotlinx.coroutines.flow.MutableStateFlow
22+
import kotlinx.coroutines.flow.StateFlow
2123
import kotlinx.coroutines.launch
2224
import kotlinx.coroutines.runBlocking
2325
import kotlinx.coroutines.sync.Mutex
@@ -126,9 +128,7 @@ class PlaceholderManager(
126128
val targetSpan = targetItem?.span
127129
val currentType = targetSpan?.attributes?.getValue(TYPE_ATTRIBUTE)
128130
if (currentType != null) {
129-
Log.d("vojta", "Item found: $currentType")
130131
if (shouldMergeItem(currentType)) {
131-
Log.d("vojta", "Updating item: $currentType")
132132
updateSpan(type, targetItem.span, targetItem.placeAtStart, updateItem, currentType)
133133
} else {
134134
val (newLinePosition, targetSelection) = if (targetItem.placeAtStart) {
@@ -141,7 +141,6 @@ class PlaceholderManager(
141141
insertItem(type, *updateItem(null, null, false).toList().toTypedArray())
142142
}
143143
} else {
144-
Log.d("vojta", "Inserting item: $type")
145144
insertItem(type, *updateItem(null, null, false).toList().toTypedArray())
146145
}
147146
}
@@ -274,12 +273,10 @@ class PlaceholderManager(
274273
suspend fun reloadAllPlaceholders() {
275274
val tempPositionToId = positionToId.toList()
276275
tempPositionToId.forEach { placeholder ->
277-
Log.d("vojta", "Looking up position to ID")
278276
val isValid = positionToIdMutex.withLock {
279277
positionToId.contains(placeholder)
280278
}
281279
if (isValid) {
282-
Log.d("vojta", "Reloading all placeholders")
283280
insertContentOverSpanWithId(placeholder.uuid)
284281
}
285282
}
@@ -304,7 +301,6 @@ class PlaceholderManager(
304301
}
305302
}
306303
val targetPosition = aztecText.getElementPosition(predicate) ?: return
307-
Log.d("vojta", "Inserting in position")
308304
insertInPosition(aztecAttributes ?: return, targetPosition)
309305
}
310306

@@ -336,7 +332,6 @@ class PlaceholderManager(
336332
parentTextViewRect.top += parentTextViewTopAndBottomOffset
337333
parentTextViewRect.bottom = parentTextViewRect.top + height
338334

339-
Log.d("vojta", "Looking for a view with tag $uuid")
340335
var box = container.findViewWithTag<View>(uuid)?.apply {
341336
id = uuid.hashCode()
342337
}
@@ -345,53 +340,59 @@ class PlaceholderManager(
345340
val padding = 10
346341
val newLeftPadding = parentTextViewRect.left + padding + aztecText.paddingStart
347342
val newTopPadding = parentTextViewRect.top + padding
348-
Log.d("vojta", "Redrawing: top padding $newTopPadding, left padding $newLeftPadding, width $newWidth, height $newHeight")
343+
var recreateView = box == null
349344
box?.let { existingView ->
350345
val currentParams = existingView.layoutParams as FrameLayout.LayoutParams
351346
val widthSame = currentParams.width == newWidth
352347
val heightSame = currentParams.height == newHeight
353348
val topMarginSame = currentParams.topMargin == newTopPadding
354349
val leftMarginSame = currentParams.leftMargin == newLeftPadding
355-
Log.d("vojta", "Same: $widthSame, $heightSame, $topMarginSame, $leftMarginSame")
356350
if (widthSame && heightSame && topMarginSame && leftMarginSame) {
357-
Log.d("vojta", "Not redrawing")
358351
return
359352
}
360-
Log.d("vojta", "Redrawing")
361-
if (!widthSame || !heightSame) {
362-
TransitionManager.beginDelayedTransition(container)
363-
}
364-
365-
container.removeView(box)
366-
positionToIdMutex.withLock {
367-
positionToId.removeAll {
368-
it.uuid == uuid
353+
val propertiesChanged = !widthSame || !heightSame
354+
recreateView = !propertiesChanged || !adapter.animateLayoutChanges()
355+
if (recreateView) {
356+
container.removeView(box)
357+
positionToIdMutex.withLock {
358+
positionToId.removeAll {
359+
it.uuid == uuid
360+
}
369361
}
370362
}
371363
}
364+
val paramsFlow = positionToId.find {
365+
it.uuid == uuid
366+
}?.viewParams ?: MutableStateFlow(Placeholder.ViewParams(newWidth, newHeight, attrs, initial = true))
367+
if (box == null || recreateView) {
368+
Log.d("vojta", "Creating new view")
369+
box = adapter.createView(container.context, uuid, paramsFlow)
370+
box.id = uuid.hashCode()
371+
box.setBackgroundColor(Color.TRANSPARENT)
372+
box.setOnTouchListener(adapter)
373+
box.tag = uuid
374+
box.layoutParams = FrameLayout.LayoutParams(
375+
newWidth,
376+
newHeight
377+
)
378+
} else {
379+
Log.d("vojta", "Updating params")
380+
paramsFlow.emit(Placeholder.ViewParams(newWidth, newHeight, attrs, initial = false))
381+
}
382+
Log.d("vojta", "Creating view with $newWidth x $newHeight")
372383

373-
box = adapter.createView(container.context, uuid, attrs)
374-
box.id = uuid.hashCode()
375-
Log.d("vojta", "Creating a new view with id: ${box.id}")
376-
box.setBackgroundColor(Color.TRANSPARENT)
377-
box.setOnTouchListener(adapter)
378-
box.tag = uuid
379-
val params = FrameLayout.LayoutParams(
380-
newWidth,
381-
newHeight
382-
)
383-
params.setMargins(
384-
newLeftPadding,
385-
newTopPadding,
386-
0,
387-
0
388-
)
389-
box.layoutParams = params
384+
box.updateLayoutParams<FrameLayout.LayoutParams> {
385+
leftMargin = newLeftPadding
386+
topMargin = newTopPadding
387+
}
390388

391389
positionToIdMutex.withLock {
392-
positionToId.add(Placeholder(targetPosition, uuid))
390+
positionToId.add(Placeholder(targetPosition, uuid, paramsFlow))
391+
}
392+
if (recreateView) {
393+
Log.d("vojta", "Adding view with $newWidth x $newHeight")
394+
container.addView(box)
393395
}
394-
container.addView(box)
395396
adapter.onViewCreated(box, uuid)
396397
}
397398

@@ -567,7 +568,7 @@ class PlaceholderManager(
567568
* @param placeholderUuid the placeholder UUID
568569
* @param attrs aztec attributes of the view
569570
*/
570-
suspend fun createView(context: Context, placeholderUuid: String, attrs: AztecAttributes): View
571+
suspend fun createView(context: Context, placeholderUuid: String, viewParamsUpdate: StateFlow<Placeholder.ViewParams>): View
571572

572573
/**
573574
* Called after the view is measured. Use this method if you need the actual width and height of the view to
@@ -577,6 +578,8 @@ class PlaceholderManager(
577578
*/
578579
suspend fun onViewCreated(view: View, placeholderUuid: String) {}
579580

581+
fun animateLayoutChanges() = false
582+
580583
/**
581584
* Called when the placeholder is deleted by the user. Use this method if you need to clear your data when the
582585
* item is deleted (for example delete an image in your DB).
@@ -668,7 +671,9 @@ class PlaceholderManager(
668671
}
669672
}
670673

671-
data class Placeholder(val elementPosition: Int, val uuid: String)
674+
data class Placeholder(val elementPosition: Int, val uuid: String, val viewParams: MutableStateFlow<ViewParams>) {
675+
data class ViewParams(val width: Int, val height: Int, val attrs: AztecAttributes, val initial: Boolean = false)
676+
}
672677

673678
companion object {
674679
private const val TAG = "PlaceholderManager"

0 commit comments

Comments
 (0)