Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f418bb02 authored by Selim Cinek's avatar Selim Cinek
Browse files

Handling multiple players better

Previously the animation was completely broken with multiple players
since they were all laid out at 0 instead of the actual position.
Measuring the full layout is a bit too expansive, so we're
introducing some workarounds to only measure the players.

Test: add multiple players, observe transitions
Bug: 154137987
Change-Id: I1c424c980cf3b64f5a9d63ba058aa7e47f6e4156
parent 8081f094
Loading
Loading
Loading
Loading
+2 −2
Original line number Original line Diff line number Diff line
@@ -16,7 +16,7 @@
  -->
  -->


<!-- Carousel for media controls -->
<!-- Carousel for media controls -->
<HorizontalScrollView
<com.android.systemui.media.UnboundHorizontalScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_height="wrap_content"
@@ -34,4 +34,4 @@
        >
        >
        <!-- QSMediaPlayers will be added here dynamically -->
        <!-- QSMediaPlayers will be added here dynamically -->
    </LinearLayout>
    </LinearLayout>
</HorizontalScrollView>
</com.android.systemui.media.UnboundHorizontalScrollView>
+16 −24
Original line number Original line Diff line number Diff line
@@ -47,6 +47,7 @@ import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.TextView;


import androidx.annotation.Nullable;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.constraintlayout.motion.widget.Key;
import androidx.constraintlayout.motion.widget.Key;
import androidx.constraintlayout.motion.widget.KeyAttributes;
import androidx.constraintlayout.motion.widget.KeyAttributes;
import androidx.constraintlayout.motion.widget.KeyFrames;
import androidx.constraintlayout.motion.widget.KeyFrames;
@@ -111,7 +112,6 @@ public class MediaControlPanel {
    private int mAlbumArtSize;
    private int mAlbumArtSize;
    private int mAlbumArtRadius;
    private int mAlbumArtRadius;
    private int mViewWidth;
    private int mViewWidth;
    private MediaMeasurementInput mLastMeasureInput;


    public static final String MEDIA_PREFERENCES = "media_control_prefs";
    public static final String MEDIA_PREFERENCES = "media_control_prefs";
    public static final String MEDIA_PREFERENCE_KEY = "browser_components";
    public static final String MEDIA_PREFERENCE_KEY = "browser_components";
@@ -188,7 +188,6 @@ public class MediaControlPanel {
        mActivityStarter = activityStarter;
        mActivityStarter = activityStarter;
        mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
        mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
        mSeekBarObserver = new SeekBarObserver(getView());
        mSeekBarObserver = new SeekBarObserver(getView());
        // TODO: we should pause this whenever the screen is off / panel is collapsed etc.
        mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
        mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
        SeekBar bar = getView().findViewById(R.id.media_progress_bar);
        SeekBar bar = getView().findViewById(R.id.media_progress_bar);
        bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
        bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
@@ -261,7 +260,7 @@ public class MediaControlPanel {
        // Try to find a browser service component for this app
        // Try to find a browser service component for this app
        // TODO also check for a media button receiver intended for restarting (b/154127084)
        // TODO also check for a media button receiver intended for restarting (b/154127084)
        // Only check if we haven't tried yet or the session token changed
        // Only check if we haven't tried yet or the session token changed
        final String pkgName = mController.getPackageName();
        final String pkgName = data.getPackageName();
        if (mServiceComponent == null && !mCheckedForResumption) {
        if (mServiceComponent == null && !mCheckedForResumption) {
            Log.d(TAG, "Checking for service component");
            Log.d(TAG, "Checking for service component");
            PackageManager pm = mContext.getPackageManager();
            PackageManager pm = mContext.getPackageManager();
@@ -301,8 +300,7 @@ public class MediaControlPanel {


        // App icon
        // App icon
        ImageView appIcon = mMediaNotifView.requireViewById(R.id.icon);
        ImageView appIcon = mMediaNotifView.requireViewById(R.id.icon);
        // TODO: look at iconDrawable
        Drawable iconDrawable = data.getAppIcon().mutate();
        Drawable iconDrawable = data.getAppIcon();
        iconDrawable.setTint(mForegroundColor);
        iconDrawable.setTint(mForegroundColor);
        appIcon.setImageDrawable(iconDrawable);
        appIcon.setImageDrawable(iconDrawable);


@@ -389,7 +387,7 @@ public class MediaControlPanel {
        }
        }


        // Seek Bar
        // Seek Bar
        final MediaController controller = new MediaController(getContext(), data.getToken());
        final MediaController controller = getController();
        mBackgroundExecutor.execute(
        mBackgroundExecutor.execute(
                () -> mSeekBarViewModel.updateController(controller, data.getForegroundColor()));
                () -> mSeekBarViewModel.updateController(controller, data.getForegroundColor()));


@@ -397,10 +395,13 @@ public class MediaControlPanel {
        // TODO: b/156036025 bring back media guts
        // TODO: b/156036025 bring back media guts


        makeActive();
        makeActive();

        // Update both constraint sets to regenerate the animation.
        mMediaNotifView.updateState(R.id.collapsed, collapsedSet);
        mMediaNotifView.updateState(R.id.collapsed, collapsedSet);
        mMediaNotifView.updateState(R.id.expanded, expandedSet);
        mMediaNotifView.updateState(R.id.expanded, expandedSet);
    }
    }


    @UiThread
    private Drawable createRoundedBitmap(Icon icon) {
    private Drawable createRoundedBitmap(Icon icon) {
        if (icon == null) {
        if (icon == null) {
            return null;
            return null;
@@ -746,26 +747,13 @@ public class MediaControlPanel {
     */
     */
    protected void removePlayer() { }
    protected void removePlayer() { }


    public void remeasure(@Nullable MediaMeasurementInput input, boolean animate, long duration,
    public void measure(@Nullable MediaMeasurementInput input) {
            long startDelay) {
        if (input != null) {
        // Let's remeasure if our width changed. Our height is dependent on the expansion, so we
        // won't animate if it changed
        if (input != null && !input.sameAs(mLastMeasureInput)) {
            mLastMeasureInput = input;
            if (animate) {
                mLayoutAnimationHelper.animatePendingSizeChange(duration, startDelay);
            }
            remeasureInternal(input);
            mMediaNotifView.layout(0, 0, mMediaNotifView.getMeasuredWidth(),
                    mMediaNotifView.getMeasuredHeight());
        }
    }

    private void remeasureInternal(MediaMeasurementInput input) {
            int width = input.getWidth();
            int width = input.getWidth();
            setPlayerWidth(width);
            setPlayerWidth(width);
            mMediaNotifView.measure(input.getWidthMeasureSpec(), input.getHeightMeasureSpec());
            mMediaNotifView.measure(input.getWidthMeasureSpec(), input.getHeightMeasureSpec());
        }
        }
    }


    public void setPlayerWidth(int width) {
    public void setPlayerWidth(int width) {
        ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded);
        ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded);
@@ -775,4 +763,8 @@ public class MediaControlPanel {
        mMediaNotifView.updateState(R.id.collapsed, collapsedSet);
        mMediaNotifView.updateState(R.id.collapsed, collapsedSet);
        mMediaNotifView.updateState(R.id.expanded, expandedSet);
        mMediaNotifView.updateState(R.id.expanded, expandedSet);
    }
    }

    public void animatePendingSizeChange(long duration, long startDelay) {
        mLayoutAnimationHelper.animatePendingSizeChange(duration, startDelay);
    }
}
}
+40 −16
Original line number Original line Diff line number Diff line
@@ -48,6 +48,11 @@ class MediaHierarchyManager @Inject constructor(
    private val mediaViewManager: MediaViewManager,
    private val mediaViewManager: MediaViewManager,
    private val mediaMeasurementProvider: MediaMeasurementManager
    private val mediaMeasurementProvider: MediaMeasurementManager
) {
) {
    /**
     * The root overlay of the hierarchy. This is where the media notification is attached to
     * whenever the view is transitioning from one host to another. It also make sure that the
     * view is always in its final state when it is attached to a view host.
     */
    private var rootOverlay: ViewGroupOverlay? = null
    private var rootOverlay: ViewGroupOverlay? = null
    private lateinit var currentState: MediaState
    private lateinit var currentState: MediaState
    private val mediaCarousel
    private val mediaCarousel
@@ -79,9 +84,24 @@ class MediaHierarchyManager @Inject constructor(
    }
    }
    private var targetState: MediaState? = null
    private var targetState: MediaState? = null
    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
    private var previousLocation = -1

    private var desiredLocation = -1
    /**
    private var currentAttachmentLocation = -1
     * The last location where this view was at before going to the desired location. This is
     * useful for guided transitions.
     */
    @MediaLocation private var previousLocation = -1

    /**
     * The desired location where the view will be at the end of the transition.
     */
    @MediaLocation private var desiredLocation = -1

    /**
     * The current attachment location where the view is currently attached.
     * Usually this matches the desired location except for animations whenever a view moves
     * to the new desired location, during which it is in [IN_OVERLAY].
     */
    @MediaLocation private var currentAttachmentLocation = -1


    var qsExpansion: Float = 0.0f
    var qsExpansion: Float = 0.0f
        set(value) {
        set(value) {
@@ -135,16 +155,12 @@ class MediaHierarchyManager @Inject constructor(
    private fun createUniqueObjectHost(host: MediaHost): UniqueObjectHostView {
    private fun createUniqueObjectHost(host: MediaHost): UniqueObjectHostView {
        val viewHost = UniqueObjectHostView(context)
        val viewHost = UniqueObjectHostView(context)
        viewHost.measurementCache = mediaMeasurementProvider.obtainCache(host)
        viewHost.measurementCache = mediaMeasurementProvider.obtainCache(host)
        viewHost.firstMeasureListener =  { input ->
        viewHost.onMeasureListener =  { input ->
            if (host.location == currentAttachmentLocation) {
            if (host.location == desiredLocation) {
                // The first measurement of the attached view is happening, Let's make
                // Measurement of the currently active player is happening, Let's make
                // sure the player width is updated
                // sure the player width is up to date
                val measuringInput = host.getMeasuringInput(input)
                val measuringInput = host.getMeasuringInput(input)
                mediaViewManager.remeasureAllPlayers(
                mediaViewManager.setPlayerWidth(measuringInput.width)
                        measuringInput,
                        animate = false,
                        duration = 0,
                        startDelay = 0)
            }
            }
        }
        }


@@ -162,6 +178,10 @@ class MediaHierarchyManager @Inject constructor(
        return viewHost
        return viewHost
    }
    }


    /**
     * Updates the location that the view should be in. If it changes, an animation may be triggered
     * going from the old desired location to the new one.
     */
    private fun updateDesiredLocation() {
    private fun updateDesiredLocation() {
        val desiredLocation = calculateLocation()
        val desiredLocation = calculateLocation()
        if (desiredLocation != this.desiredLocation) {
        if (desiredLocation != this.desiredLocation) {
@@ -173,7 +193,7 @@ class MediaHierarchyManager @Inject constructor(
            // Let's perform a transition
            // Let's perform a transition
            val animate = shouldAnimateTransition(desiredLocation, previousLocation)
            val animate = shouldAnimateTransition(desiredLocation, previousLocation)
            val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
            val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
            mediaViewManager.onDesiredStateChanged(getHost(desiredLocation)?.currentState,
            mediaViewManager.onDesiredLocationChanged(getHost(desiredLocation)?.currentState,
                    animate, animDuration, delay)
                    animate, animDuration, delay)
            performTransitionToNewLocation(isNewView, animate)
            performTransitionToNewLocation(isNewView, animate)
        }
        }
@@ -262,6 +282,9 @@ class MediaHierarchyManager @Inject constructor(
        }
        }
    }
    }


    /**
     * Updates the state that the view wants to be in at the end of the animation.
     */
    private fun updateTargetState() {
    private fun updateTargetState() {
        if (isCurrentlyInGuidedTransformation()) {
        if (isCurrentlyInGuidedTransformation()) {
            val progress = getTransformationProgress()
            val progress = getTransformationProgress()
@@ -320,17 +343,17 @@ class MediaHierarchyManager @Inject constructor(
    }
    }


    private fun applyState(state: MediaState) {
    private fun applyState(state: MediaState) {
        currentState = state.copy()
        mediaViewManager.setCurrentState(currentState)
        updateHostAttachment()
        updateHostAttachment()
        val boundsOnScreen = state.boundsOnScreen
        if (currentAttachmentLocation == IN_OVERLAY) {
        if (currentAttachmentLocation == IN_OVERLAY) {
            val boundsOnScreen = state.boundsOnScreen
            mediaCarousel.setLeftTopRightBottom(
            mediaCarousel.setLeftTopRightBottom(
                    boundsOnScreen.left,
                    boundsOnScreen.left,
                    boundsOnScreen.top,
                    boundsOnScreen.top,
                    boundsOnScreen.right,
                    boundsOnScreen.right,
                    boundsOnScreen.bottom)
                    boundsOnScreen.bottom)
        }
        }
        currentState = state.copy()
        mediaViewManager.setCurrentState(currentState)
    }
    }


    private fun updateHostAttachment() {
    private fun updateHostAttachment() {
@@ -348,6 +371,7 @@ class MediaHierarchyManager @Inject constructor(
                rootOverlay!!.add(mediaCarousel)
                rootOverlay!!.add(mediaCarousel)
            } else {
            } else {
                targetHost.addView(mediaCarousel)
                targetHost.addView(mediaCarousel)
                mediaViewManager.onViewReattached()
            }
            }
        }
        }
    }
    }
+0 −1
Original line number Original line Diff line number Diff line
@@ -7,7 +7,6 @@ import android.view.View.OnAttachStateChangeListener
import android.view.ViewGroup
import android.view.ViewGroup
import com.android.systemui.media.MediaHierarchyManager.MediaLocation
import com.android.systemui.media.MediaHierarchyManager.MediaLocation
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.MeasurementInputData
import javax.inject.Inject
import javax.inject.Inject


class MediaHost @Inject constructor(
class MediaHost @Inject constructor(
+122 −19
Original line number Original line Diff line number Diff line
@@ -2,7 +2,9 @@ package com.android.systemui.media


import android.content.Context
import android.content.Context
import android.view.LayoutInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import android.widget.LinearLayout
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.InfoMediaManager
import com.android.settingslib.media.InfoMediaManager
@@ -33,12 +35,16 @@ class MediaViewManager @Inject constructor(
    private val activityStarter: ActivityStarter,
    private val activityStarter: ActivityStarter,
    mediaManager: MediaDataManager
    mediaManager: MediaDataManager
) {
) {
    private var playerWidth: Int = 0
    private var playerWidthPlusPadding: Int = 0
    private var desiredState: MediaHost.MediaHostState? = null
    private var desiredState: MediaHost.MediaHostState? = null
    private var currentState: MediaState? = null
    private var currentState: MediaState? = null
    val mediaCarousel: ViewGroup
    val mediaCarousel: HorizontalScrollView
    private val mediaContent: ViewGroup
    private val mediaContent: ViewGroup
    private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
    private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
    private val visualStabilityCallback = ::reorderAllPlayers
    private val visualStabilityCallback = ::reorderAllPlayers
    private var activeMediaIndex: Int = 0
    private var scrollIntoCurrentMedia: Int = 0


    private var currentlyExpanded = true
    private var currentlyExpanded = true
        set(value) {
        set(value) {
@@ -49,29 +55,50 @@ class MediaViewManager @Inject constructor(
                }
                }
            }
            }
        }
        }
    private val scrollChangedListener = object : View.OnScrollChangeListener {
        override fun onScrollChange(v: View?, scrollX: Int, scrollY: Int, oldScrollX: Int,
                                    oldScrollY: Int) {
            if (playerWidthPlusPadding == 0) {
                return
            }
            onMediaScrollingChanged(scrollX / playerWidthPlusPadding,
                    scrollX % playerWidthPlusPadding)
        }
    }


    init {
    init {
        mediaCarousel = inflateMediaCarousel()
        mediaCarousel = inflateMediaCarousel()
        mediaCarousel.setOnScrollChangeListener(scrollChangedListener)
        mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
        mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
        mediaManager.addListener(object : MediaDataManager.Listener {
        mediaManager.addListener(object : MediaDataManager.Listener {
            override fun onMediaDataLoaded(key: String, data: MediaData) {
            override fun onMediaDataLoaded(key: String, data: MediaData) {
                updateView(key, data)
                updateView(key, data)
                updatePlayerVisibilities()
            }
            }


            override fun onMediaDataRemoved(key: String) {
            override fun onMediaDataRemoved(key: String) {
                val removed = mediaPlayers.remove(key)
                val removed = mediaPlayers.remove(key)
                removed?.apply {
                removed?.apply {
                    val beforeActive = mediaContent.indexOfChild(removed.view) <= activeMediaIndex
                    mediaContent.removeView(removed.view)
                    mediaContent.removeView(removed.view)
                    removed.onDestroy()
                    removed.onDestroy()
                    updateMediaPaddings()
                    updateMediaPaddings()
                    if (beforeActive) {
                        // also update the index here since the scroll below might not always lead
                        // to a scrolling changed
                        activeMediaIndex = Math.max(0, activeMediaIndex - 1)
                        mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX
                                - playerWidthPlusPadding, 0)
                    }
                    updatePlayerVisibilities()
                }
                }
            }
            }
        })
        })
    }
    }


    private fun inflateMediaCarousel(): ViewGroup {
    private fun inflateMediaCarousel(): HorizontalScrollView {
        return LayoutInflater.from(context).inflate(
        return LayoutInflater.from(context).inflate(R.layout.media_carousel,
                R.layout.media_carousel, UniqueObjectHostView(context), false) as ViewGroup
                UniqueObjectHostView(context), false) as HorizontalScrollView
    }
    }


    private fun reorderAllPlayers() {
    private fun reorderAllPlayers() {
@@ -83,6 +110,26 @@ class MediaViewManager @Inject constructor(
            }
            }
        }
        }
        updateMediaPaddings()
        updateMediaPaddings()
        updatePlayerVisibilities()
    }

    private fun onMediaScrollingChanged(newIndex: Int, scrollInAmount: Int) {
        val wasScrolledIn = scrollIntoCurrentMedia != 0
        scrollIntoCurrentMedia = scrollInAmount
        val nowScrolledIn = scrollIntoCurrentMedia != 0
        if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) {
            activeMediaIndex = newIndex
            updatePlayerVisibilities()
        }
    }

    private fun updatePlayerVisibilities() {
        val scrolledIn = scrollIntoCurrentMedia != 0
        for (i in 0 until mediaContent.childCount) {
            val view = mediaContent.getChildAt(i)
            val visible = (i == activeMediaIndex) || ((i == (activeMediaIndex + 1)) && scrolledIn)
            view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
        }
    }
    }


    private fun updateView(key: String, data: MediaData) {
    private fun updateView(key: String, data: MediaData) {
@@ -127,8 +174,7 @@ class MediaViewManager @Inject constructor(
    private fun updatePlayerToCurrentState(existingPlayer: MediaControlPanel) {
    private fun updatePlayerToCurrentState(existingPlayer: MediaControlPanel) {
        if (desiredState != null && desiredState!!.measurementInput != null) {
        if (desiredState != null && desiredState!!.measurementInput != null) {
            // make sure the player width is set to the current state
            // make sure the player width is set to the current state
            val measurementInput = desiredState!!.measurementInput!!
            existingPlayer.setPlayerWidth(playerWidth)
            existingPlayer.setPlayerWidth(measurementInput.width)
        }
        }
    }
    }


@@ -147,6 +193,10 @@ class MediaViewManager @Inject constructor(


    }
    }


    /**
     * Set the current state of a view. This is updated often during animations and we shouldn't
     * do anything expensive.
     */
    fun setCurrentState(state: MediaState) {
    fun setCurrentState(state: MediaState) {
        currentState = state
        currentState = state
        currentlyExpanded = state.expansion > 0
        currentlyExpanded = state.expansion > 0
@@ -157,23 +207,58 @@ class MediaViewManager @Inject constructor(
    }
    }


    /**
    /**
     * @param targetState the target state we're transitioning to
     * The desired location of this view has changed. We should remeasure the view to match
     * the new bounds and kick off bounds animations if necessary.
     * If an animation is happening, an animation is kicked of externally, which sets a new
     * current state until we reach the targetState.
     *
     * @param desiredState the target state we're transitioning to
     * @param animate should this be animated
     * @param animate should this be animated
     */
     */
    fun onDesiredStateChanged(targetState: MediaState?, animate: Boolean, duration: Long,
    fun onDesiredLocationChanged(desiredState: MediaState?, animate: Boolean, duration: Long,
                                 startDelay: Long) {
                                 startDelay: Long) {
        if (targetState is MediaHost.MediaHostState) {
        if (desiredState is MediaHost.MediaHostState) {
            // This is a hosting view, let's remeasure our players
            // This is a hosting view, let's remeasure our players
            desiredState = targetState
            this.desiredState = desiredState
            val measurementInput = targetState.measurementInput
            val width = desiredState.boundsOnScreen.width()
            remeasureAllPlayers(measurementInput, animate, duration, startDelay)
            if (playerWidth != width) {
                setPlayerWidth(width)
                for (mediaPlayer in mediaPlayers.values) {
                    if (animate && mediaPlayer.view.visibility == View.VISIBLE) {
                        mediaPlayer.animatePendingSizeChange(duration, startDelay)
                    }
                }
                val widthSpec = desiredState.measurementInput?.widthMeasureSpec ?: 0
                val heightSpec = desiredState.measurementInput?.heightMeasureSpec ?: 0
                var left = 0
                for (i in 0 until mediaContent.childCount) {
                    val view = mediaContent.getChildAt(i)
                    view.measure(widthSpec, heightSpec)
                    view.layout(left, 0, left + width, view.measuredHeight)
                    left = left + playerWidthPlusPadding
                }
            }
        }
        }
    }
    }


    fun remeasureAllPlayers(measurementInput: MediaMeasurementInput?,
    fun setPlayerWidth(width: Int) {
                                    animate: Boolean, duration: Long, startDelay: Long) {
        if (width != playerWidth) {
            playerWidth = width
            playerWidthPlusPadding = playerWidth + context.resources.getDimensionPixelSize(
                    R.dimen.qs_media_padding)
            for (mediaPlayer in mediaPlayers.values) {
            for (mediaPlayer in mediaPlayers.values) {
            mediaPlayer.remeasure(measurementInput, animate, duration, startDelay)
                mediaPlayer.setPlayerWidth(width)
            }
            // The player width has changed, let's update the scroll position to make sure
            // it's still at the same place
            var newScroll = activeMediaIndex * playerWidthPlusPadding
            if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
                newScroll += playerWidthPlusPadding
                - (scrollIntoCurrentMedia - playerWidthPlusPadding)
            } else {
                newScroll += scrollIntoCurrentMedia
            }
            mediaCarousel.scrollX = newScroll
        }
        }
    }
    }


@@ -185,15 +270,33 @@ class MediaViewManager @Inject constructor(
        val firstPlayer = mediaPlayers.values.firstOrNull() ?: return null
        val firstPlayer = mediaPlayers.values.firstOrNull() ?: return null
        // Let's measure the size of the first player and return its height
        // Let's measure the size of the first player and return its height
        val previousProgress = firstPlayer.view.progress
        val previousProgress = firstPlayer.view.progress
        val previousRight = firstPlayer.view.right
        val previousBottom = firstPlayer.view.bottom
        firstPlayer.view.progress = input.expansion
        firstPlayer.view.progress = input.expansion
        firstPlayer.remeasure(input, false /* animate */, 0, 0)
        firstPlayer.measure(input)
        // Relayouting is necessary in motionlayout to obtain its size properly ....
        firstPlayer.view.layout(0, 0, firstPlayer.view.measuredWidth,
                firstPlayer.view.measuredHeight)
        val result = MeasurementOutput(firstPlayer.view.measuredWidth,
        val result = MeasurementOutput(firstPlayer.view.measuredWidth,
                firstPlayer.view.measuredHeight)
                firstPlayer.view.measuredHeight)
        firstPlayer.view.progress = previousProgress
        firstPlayer.view.progress = previousProgress
        if (desiredState != null) {
        if (desiredState != null) {
            // remeasure it to the old size again!
            // remeasure it to the old size again!
            firstPlayer.remeasure(desiredState!!.measurementInput, false, 0, 0)
            firstPlayer.measure(desiredState!!.measurementInput)
            firstPlayer.view.layout(0, 0, previousRight, previousBottom)
        }
        }
        return result
        return result
    }
    }

    fun onViewReattached() {
        if (desiredState is MediaHost.MediaHostState) {
            // HACK: MotionLayout doesn't always properly reevalate the state, let's kick of
            // a measure to force it.
            val widthSpec = desiredState!!.measurementInput?.widthMeasureSpec ?: 0
            val heightSpec = desiredState!!.measurementInput?.heightMeasureSpec ?: 0
            for (mediaPlayer in mediaPlayers.values) {
                mediaPlayer.view.measure(widthSpec, heightSpec)
            }
        }
    }
}
}
 No newline at end of file
Loading