Loading packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +36 −10 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package com.android.systemui.media import android.content.Context import android.content.Intent import android.content.res.Configuration import android.graphics.Color import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.view.LayoutInflater Loading Loading @@ -96,7 +97,6 @@ class MediaCarouselController @Inject constructor( * The measured height of the carousel */ private var carouselMeasureHeight: Int = 0 private var playerWidthPlusPadding: Int = 0 private var desiredHostState: MediaHostState? = null private val mediaCarousel: MediaScrollView private val mediaCarouselScrollHandler: MediaCarouselScrollHandler Loading @@ -108,6 +108,15 @@ class MediaCarouselController @Inject constructor( private val pageIndicator: PageIndicator private val visualStabilityCallback: VisualStabilityManager.Callback private var needsReordering: Boolean = false private var isRtl: Boolean = false set(value) { if (value != field) { field = value mediaFrame.layoutDirection = if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR mediaCarouselScrollHandler.scrollToStart() } } private var currentlyExpanded = true set(value) { if (field != value) { Loading @@ -126,6 +135,11 @@ class MediaCarouselController @Inject constructor( override fun onOverlayChanged() { inflateSettingsButton() } override fun onConfigChanged(newConfig: Configuration?) { if (newConfig == null) return isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL } } init { Loading @@ -135,6 +149,7 @@ class MediaCarouselController @Inject constructor( mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator, executor, mediaDataManager::onSwipeToDismiss, this::updatePageIndicatorLocation, falsingManager) isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL inflateSettingsButton() mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) configurationController.addCallback(configListener) Loading @@ -144,7 +159,7 @@ class MediaCarouselController @Inject constructor( reorderAllPlayers() } // Let's reset our scroll position mediaCarousel.scrollX = 0 mediaCarouselScrollHandler.scrollToStart() } visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback, true /* persistent */) Loading Loading @@ -196,8 +211,13 @@ class MediaCarouselController @Inject constructor( } private fun inflateMediaCarousel(): ViewGroup { return LayoutInflater.from(context).inflate(R.layout.media_carousel, val mediaCarousel = LayoutInflater.from(context).inflate(R.layout.media_carousel, UniqueObjectHostView(context), false) as ViewGroup // Because this is inflated when not attached to the true view hierarchy, it resolves some // potential issues to force that the layout direction is defined by the locale // (rather than inherited from the parent, which would resolve to LTR when unattached). mediaCarousel.layoutDirection = View.LAYOUT_DIRECTION_LOCALE return mediaCarousel } private fun reorderAllPlayers() { Loading Loading @@ -313,8 +333,12 @@ class MediaCarouselController @Inject constructor( private fun updatePageIndicatorLocation() { // Update the location of the page indicator, carousel clipping pageIndicator.translationX = (currentCarouselWidth - pageIndicator.width) / 2.0f + mediaCarouselScrollHandler.contentTranslation val translationX = if (isRtl) { (pageIndicator.width - currentCarouselWidth) / 2.0f } else { (currentCarouselWidth - pageIndicator.width) / 2.0f } pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams pageIndicator.translationY = (currentCarouselHeight - pageIndicator.height - layoutParams.bottomMargin).toFloat() Loading @@ -334,7 +358,8 @@ class MediaCarouselController @Inject constructor( if (width != currentCarouselWidth || height != currentCarouselHeight) { currentCarouselWidth = width currentCarouselHeight = height mediaCarouselScrollHandler.setCarouselBounds(currentCarouselWidth, currentCarouselHeight) mediaCarouselScrollHandler.setCarouselBounds( currentCarouselWidth, currentCarouselHeight) updatePageIndicatorLocation() } } Loading @@ -348,7 +373,7 @@ class MediaCarouselController @Inject constructor( if (currentlyShowingOnlyActive != endShowsActive || ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) && startShowsActive != endShowsActive)) { /// Whenever we're transitioning from between differing states or the endstate differs // Whenever we're transitioning from between differing states or the endstate differs // we reset the translation currentlyShowingOnlyActive = endShowsActive mediaCarouselScrollHandler.resetTranslation(animate = true) Loading Loading @@ -416,14 +441,15 @@ class MediaCarouselController @Inject constructor( height != carouselMeasureWidth && height != 0) { carouselMeasureWidth = width carouselMeasureHeight = height playerWidthPlusPadding = carouselMeasureWidth + context.resources.getDimensionPixelSize( R.dimen.qs_media_padding) mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding val playerWidthPlusPadding = carouselMeasureWidth + context.resources.getDimensionPixelSize(R.dimen.qs_media_padding) // Let's remeasure the carousel val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0 val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0 mediaCarousel.measure(widthSpec, heightSpec) mediaCarousel.layout(0, 0, width, mediaCarousel.measuredHeight) // Update the padding after layout; view widths are used in RTL to calculate scrollX mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding } } } packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt +68 −32 Original line number Diff line number Diff line Loading @@ -58,6 +58,10 @@ class MediaCarouselScrollHandler( private var translationChangedListener: () -> Unit, private val falsingManager: FalsingManager ) { /** * Is the view in RTL */ val isRtl: Boolean get() = scrollView.isLayoutRtl /** * Do we need falsing protection? */ Loading Loading @@ -121,14 +125,14 @@ class MediaCarouselScrollHandler( field = value // 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 var newRelativeScroll = activeMediaIndex * playerWidthPlusPadding if (scrollIntoCurrentMedia > playerWidthPlusPadding) { newScroll += playerWidthPlusPadding - newRelativeScroll += playerWidthPlusPadding - (scrollIntoCurrentMedia - playerWidthPlusPadding) } else { newScroll += scrollIntoCurrentMedia newRelativeScroll += scrollIntoCurrentMedia } scrollView.scrollX = newScroll scrollView.relativeScrollX = newRelativeScroll } /** Loading Loading @@ -184,8 +188,9 @@ class MediaCarouselScrollHandler( if (playerWidthPlusPadding == 0) { return } onMediaScrollingChanged(scrollX / playerWidthPlusPadding, scrollX % playerWidthPlusPadding) val relativeScrollX = scrollView.relativeScrollX onMediaScrollingChanged(relativeScrollX / playerWidthPlusPadding, relativeScrollX % playerWidthPlusPadding) } } Loading Loading @@ -222,11 +227,19 @@ class MediaCarouselScrollHandler( Math.abs(contentTranslation)) val settingsTranslation = (1.0f - settingsOffset) * -settingsButton.width * SETTINGS_BUTTON_TRANSLATION_FRACTION val newTranslationX: Float val newTranslationX = if (isRtl) { // In RTL, the 0-placement is on the right side of the view, not the left... if (contentTranslation > 0) { -(scrollView.width - settingsTranslation - settingsButton.width) } else { -settingsTranslation } } else { if (contentTranslation > 0) { newTranslationX = settingsTranslation settingsTranslation } else { newTranslationX = scrollView.width - settingsTranslation - settingsButton.width scrollView.width - settingsTranslation - settingsButton.width } } val rotation = (1.0f - settingsOffset) * 50 settingsButton.rotation = rotation * -Math.signum(contentTranslation) Loading Loading @@ -259,26 +272,26 @@ class MediaCarouselScrollHandler( } if (isUp || motionEvent.action == MotionEvent.ACTION_CANCEL) { // It's an up and the fling didn't take it above val pos = scrollView.scrollX % playerWidthPlusPadding val scollXAmount: Int if (pos > playerWidthPlusPadding / 2) { scollXAmount = playerWidthPlusPadding - pos val relativePos = scrollView.relativeScrollX % playerWidthPlusPadding val scrollXAmount: Int if (relativePos > playerWidthPlusPadding / 2) { scrollXAmount = playerWidthPlusPadding - relativePos } else { scollXAmount = -1 * pos scrollXAmount = -1 * relativePos } if (scollXAmount != 0) { if (scrollXAmount != 0) { // Delay the scrolling since scrollView calls springback which cancels // the animation again.. mainExecutor.execute { scrollView.smoothScrollBy(scollXAmount, 0) scrollView.smoothScrollBy(if (isRtl) -scrollXAmount else scrollXAmount, 0) } } val currentTranslation = scrollView.getContentTranslation() if (currentTranslation != 0.0f) { // We started a Swipe but didn't end up with a fling. Let's either go to the // dismissed position or go back. val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 || isFalseTouch() val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 || isFalseTouch() val newTranslation: Float if (springBack) { newTranslation = 0.0f Loading Loading @@ -313,9 +326,11 @@ class MediaCarouselScrollHandler( return gestureDetector.onTouchEvent(motionEvent) } fun onScroll(down: MotionEvent, fun onScroll( down: MotionEvent, lastMotion: MotionEvent, distanceX: Float): Boolean { distanceX: Float ): Boolean { val totalX = lastMotion.x - down.x val currentTranslation = scrollView.getContentTranslation() if (currentTranslation != 0.0f || Loading @@ -339,8 +354,8 @@ class MediaCarouselScrollHandler( } // Otherwise we don't have do do anything, and will remove the unrubberbanded // translation } if (Math.signum(newTranslation) != Math.signum(currentTranslation) && currentTranslation != 0.0f) { if (Math.signum(newTranslation) != Math.signum(currentTranslation) && currentTranslation != 0.0f) { // We crossed the 0.0 threshold of the translation. Let's see if we're allowed // to scroll into the new direction if (scrollView.canScrollHorizontally(-newTranslation.toInt())) { Loading Loading @@ -394,9 +409,10 @@ class MediaCarouselScrollHandler( scrollView.animationTargetX = newTranslation } else { // We're flinging the player! Let's go either to the previous or to the next player val pos = scrollView.scrollX val pos = scrollView.relativeScrollX val currentIndex = if (playerWidthPlusPadding > 0) pos / playerWidthPlusPadding else 0 var destIndex = if (vX <= 0) currentIndex + 1 else currentIndex val flungTowardEnd = if (isRtl) vX > 0 else vX < 0 var destIndex = if (flungTowardEnd) currentIndex + 1 else currentIndex destIndex = Math.max(0, destIndex) destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex) val view = mediaContent.getChildAt(destIndex) Loading Loading @@ -438,8 +454,14 @@ class MediaCarouselScrollHandler( activeMediaIndex = newIndex updatePlayerVisibilities() } val location = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0) val relativeLocation = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0) scrollInAmount.toFloat() / playerWidthPlusPadding else 0f // Fix the location, because PageIndicator does not handle RTL internally val location = if (isRtl) { mediaContent.childCount - relativeLocation - 1 } else { relativeLocation } pageIndicator.setLocation(location) updateClipToOutline() } Loading Loading @@ -480,13 +502,20 @@ class MediaCarouselScrollHandler( * where it was and update our scroll position. */ fun onPrePlayerRemoved(removed: MediaControlPanel) { val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= activeMediaIndex val removedIndex = mediaContent.indexOfChild(removed.view?.player) // If the removed index is less than the activeMediaIndex, then we need to decrement it. // RTL has no effect on this, because indices are always relative (start-to-end). // Update the index 'manually' since we won't always get a call to onMediaScrollingChanged val beforeActive = removedIndex <= activeMediaIndex 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) scrollView.scrollX = Math.max(scrollView.scrollX - playerWidthPlusPadding, 0) } // If the removed media item is "left of" the active one (in an absolute sense), we need to // scroll the view to keep that player in view. This is because scroll position is always // calculated from left to right. val leftOfActive = if (isRtl) !beforeActive else beforeActive if (leftOfActive) { scrollView.scrollX = Math.max(scrollView.scrollX - playerWidthPlusPadding, 0) } } Loading @@ -501,6 +530,13 @@ class MediaCarouselScrollHandler( } } /** * Reset the MediaScrollView to the start. */ fun scrollToStart() { scrollView.relativeScrollX = 0 } companion object { private val CONTENT_TRANSLATION = object : FloatPropertyCompat<MediaCarouselScrollHandler>( "contentTranslation") { Loading packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt +38 −7 Original line number Diff line number Diff line Loading @@ -15,7 +15,10 @@ import com.android.systemui.util.animation.physicsAnimator * when only measuring children but not the parent, when trying to apply a new scroll position */ class MediaScrollView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : HorizontalScrollView(context, attrs, defStyleAttr) { lateinit var contentContainer: ViewGroup Loading @@ -37,6 +40,26 @@ class MediaScrollView @JvmOverloads constructor( contentContainer.translationX } /** * Convert between the absolute (left-to-right) and relative (start-to-end) scrollX of the media * carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX * is always absolute. This function is its own inverse. */ private fun transformScrollX(scrollX: Int): Int = if (isLayoutRtl) { contentContainer.width - width - scrollX } else { scrollX } /** * Get the layoutDirection-relative (start-to-end) scroll X position of the carousel. */ var relativeScrollX: Int get() = transformScrollX(scrollX) set(value) { scrollX = transformScrollX(value) } /** * Allow all scrolls to go through, use base implementation */ Loading @@ -55,15 +78,15 @@ class MediaScrollView @JvmOverloads constructor( } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { var intercept = false; var intercept = false touchListener?.let { intercept = it.onInterceptTouchEvent(ev) } return super.onInterceptTouchEvent(ev) || intercept; return super.onInterceptTouchEvent(ev) || intercept } override fun onTouchEvent(ev: MotionEvent?): Boolean { var touch = false; var touch = false touchListener?.let { touch = it.onTouchEvent(ev) } Loading @@ -75,9 +98,17 @@ class MediaScrollView @JvmOverloads constructor( contentContainer = getChildAt(0) as ViewGroup } override fun overScrollBy(deltaX: Int, deltaY: Int, scrollX: Int, scrollY: Int, scrollRangeX: Int, scrollRangeY: Int, maxOverScrollX: Int, maxOverScrollY: Int, isTouchEvent: Boolean): Boolean { override fun overScrollBy( deltaX: Int, deltaY: Int, scrollX: Int, scrollY: Int, scrollRangeX: Int, scrollRangeY: Int, maxOverScrollX: Int, maxOverScrollY: Int, isTouchEvent: Boolean ): Boolean { if (getContentTranslation() != 0.0f) { // When we're dismissing we ignore all the scrolling return false Loading Loading
packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +36 −10 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package com.android.systemui.media import android.content.Context import android.content.Intent import android.content.res.Configuration import android.graphics.Color import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.view.LayoutInflater Loading Loading @@ -96,7 +97,6 @@ class MediaCarouselController @Inject constructor( * The measured height of the carousel */ private var carouselMeasureHeight: Int = 0 private var playerWidthPlusPadding: Int = 0 private var desiredHostState: MediaHostState? = null private val mediaCarousel: MediaScrollView private val mediaCarouselScrollHandler: MediaCarouselScrollHandler Loading @@ -108,6 +108,15 @@ class MediaCarouselController @Inject constructor( private val pageIndicator: PageIndicator private val visualStabilityCallback: VisualStabilityManager.Callback private var needsReordering: Boolean = false private var isRtl: Boolean = false set(value) { if (value != field) { field = value mediaFrame.layoutDirection = if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR mediaCarouselScrollHandler.scrollToStart() } } private var currentlyExpanded = true set(value) { if (field != value) { Loading @@ -126,6 +135,11 @@ class MediaCarouselController @Inject constructor( override fun onOverlayChanged() { inflateSettingsButton() } override fun onConfigChanged(newConfig: Configuration?) { if (newConfig == null) return isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL } } init { Loading @@ -135,6 +149,7 @@ class MediaCarouselController @Inject constructor( mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator, executor, mediaDataManager::onSwipeToDismiss, this::updatePageIndicatorLocation, falsingManager) isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL inflateSettingsButton() mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) configurationController.addCallback(configListener) Loading @@ -144,7 +159,7 @@ class MediaCarouselController @Inject constructor( reorderAllPlayers() } // Let's reset our scroll position mediaCarousel.scrollX = 0 mediaCarouselScrollHandler.scrollToStart() } visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback, true /* persistent */) Loading Loading @@ -196,8 +211,13 @@ class MediaCarouselController @Inject constructor( } private fun inflateMediaCarousel(): ViewGroup { return LayoutInflater.from(context).inflate(R.layout.media_carousel, val mediaCarousel = LayoutInflater.from(context).inflate(R.layout.media_carousel, UniqueObjectHostView(context), false) as ViewGroup // Because this is inflated when not attached to the true view hierarchy, it resolves some // potential issues to force that the layout direction is defined by the locale // (rather than inherited from the parent, which would resolve to LTR when unattached). mediaCarousel.layoutDirection = View.LAYOUT_DIRECTION_LOCALE return mediaCarousel } private fun reorderAllPlayers() { Loading Loading @@ -313,8 +333,12 @@ class MediaCarouselController @Inject constructor( private fun updatePageIndicatorLocation() { // Update the location of the page indicator, carousel clipping pageIndicator.translationX = (currentCarouselWidth - pageIndicator.width) / 2.0f + mediaCarouselScrollHandler.contentTranslation val translationX = if (isRtl) { (pageIndicator.width - currentCarouselWidth) / 2.0f } else { (currentCarouselWidth - pageIndicator.width) / 2.0f } pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams pageIndicator.translationY = (currentCarouselHeight - pageIndicator.height - layoutParams.bottomMargin).toFloat() Loading @@ -334,7 +358,8 @@ class MediaCarouselController @Inject constructor( if (width != currentCarouselWidth || height != currentCarouselHeight) { currentCarouselWidth = width currentCarouselHeight = height mediaCarouselScrollHandler.setCarouselBounds(currentCarouselWidth, currentCarouselHeight) mediaCarouselScrollHandler.setCarouselBounds( currentCarouselWidth, currentCarouselHeight) updatePageIndicatorLocation() } } Loading @@ -348,7 +373,7 @@ class MediaCarouselController @Inject constructor( if (currentlyShowingOnlyActive != endShowsActive || ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) && startShowsActive != endShowsActive)) { /// Whenever we're transitioning from between differing states or the endstate differs // Whenever we're transitioning from between differing states or the endstate differs // we reset the translation currentlyShowingOnlyActive = endShowsActive mediaCarouselScrollHandler.resetTranslation(animate = true) Loading Loading @@ -416,14 +441,15 @@ class MediaCarouselController @Inject constructor( height != carouselMeasureWidth && height != 0) { carouselMeasureWidth = width carouselMeasureHeight = height playerWidthPlusPadding = carouselMeasureWidth + context.resources.getDimensionPixelSize( R.dimen.qs_media_padding) mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding val playerWidthPlusPadding = carouselMeasureWidth + context.resources.getDimensionPixelSize(R.dimen.qs_media_padding) // Let's remeasure the carousel val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0 val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0 mediaCarousel.measure(widthSpec, heightSpec) mediaCarousel.layout(0, 0, width, mediaCarousel.measuredHeight) // Update the padding after layout; view widths are used in RTL to calculate scrollX mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding } } }
packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt +68 −32 Original line number Diff line number Diff line Loading @@ -58,6 +58,10 @@ class MediaCarouselScrollHandler( private var translationChangedListener: () -> Unit, private val falsingManager: FalsingManager ) { /** * Is the view in RTL */ val isRtl: Boolean get() = scrollView.isLayoutRtl /** * Do we need falsing protection? */ Loading Loading @@ -121,14 +125,14 @@ class MediaCarouselScrollHandler( field = value // 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 var newRelativeScroll = activeMediaIndex * playerWidthPlusPadding if (scrollIntoCurrentMedia > playerWidthPlusPadding) { newScroll += playerWidthPlusPadding - newRelativeScroll += playerWidthPlusPadding - (scrollIntoCurrentMedia - playerWidthPlusPadding) } else { newScroll += scrollIntoCurrentMedia newRelativeScroll += scrollIntoCurrentMedia } scrollView.scrollX = newScroll scrollView.relativeScrollX = newRelativeScroll } /** Loading Loading @@ -184,8 +188,9 @@ class MediaCarouselScrollHandler( if (playerWidthPlusPadding == 0) { return } onMediaScrollingChanged(scrollX / playerWidthPlusPadding, scrollX % playerWidthPlusPadding) val relativeScrollX = scrollView.relativeScrollX onMediaScrollingChanged(relativeScrollX / playerWidthPlusPadding, relativeScrollX % playerWidthPlusPadding) } } Loading Loading @@ -222,11 +227,19 @@ class MediaCarouselScrollHandler( Math.abs(contentTranslation)) val settingsTranslation = (1.0f - settingsOffset) * -settingsButton.width * SETTINGS_BUTTON_TRANSLATION_FRACTION val newTranslationX: Float val newTranslationX = if (isRtl) { // In RTL, the 0-placement is on the right side of the view, not the left... if (contentTranslation > 0) { -(scrollView.width - settingsTranslation - settingsButton.width) } else { -settingsTranslation } } else { if (contentTranslation > 0) { newTranslationX = settingsTranslation settingsTranslation } else { newTranslationX = scrollView.width - settingsTranslation - settingsButton.width scrollView.width - settingsTranslation - settingsButton.width } } val rotation = (1.0f - settingsOffset) * 50 settingsButton.rotation = rotation * -Math.signum(contentTranslation) Loading Loading @@ -259,26 +272,26 @@ class MediaCarouselScrollHandler( } if (isUp || motionEvent.action == MotionEvent.ACTION_CANCEL) { // It's an up and the fling didn't take it above val pos = scrollView.scrollX % playerWidthPlusPadding val scollXAmount: Int if (pos > playerWidthPlusPadding / 2) { scollXAmount = playerWidthPlusPadding - pos val relativePos = scrollView.relativeScrollX % playerWidthPlusPadding val scrollXAmount: Int if (relativePos > playerWidthPlusPadding / 2) { scrollXAmount = playerWidthPlusPadding - relativePos } else { scollXAmount = -1 * pos scrollXAmount = -1 * relativePos } if (scollXAmount != 0) { if (scrollXAmount != 0) { // Delay the scrolling since scrollView calls springback which cancels // the animation again.. mainExecutor.execute { scrollView.smoothScrollBy(scollXAmount, 0) scrollView.smoothScrollBy(if (isRtl) -scrollXAmount else scrollXAmount, 0) } } val currentTranslation = scrollView.getContentTranslation() if (currentTranslation != 0.0f) { // We started a Swipe but didn't end up with a fling. Let's either go to the // dismissed position or go back. val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 || isFalseTouch() val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 || isFalseTouch() val newTranslation: Float if (springBack) { newTranslation = 0.0f Loading Loading @@ -313,9 +326,11 @@ class MediaCarouselScrollHandler( return gestureDetector.onTouchEvent(motionEvent) } fun onScroll(down: MotionEvent, fun onScroll( down: MotionEvent, lastMotion: MotionEvent, distanceX: Float): Boolean { distanceX: Float ): Boolean { val totalX = lastMotion.x - down.x val currentTranslation = scrollView.getContentTranslation() if (currentTranslation != 0.0f || Loading @@ -339,8 +354,8 @@ class MediaCarouselScrollHandler( } // Otherwise we don't have do do anything, and will remove the unrubberbanded // translation } if (Math.signum(newTranslation) != Math.signum(currentTranslation) && currentTranslation != 0.0f) { if (Math.signum(newTranslation) != Math.signum(currentTranslation) && currentTranslation != 0.0f) { // We crossed the 0.0 threshold of the translation. Let's see if we're allowed // to scroll into the new direction if (scrollView.canScrollHorizontally(-newTranslation.toInt())) { Loading Loading @@ -394,9 +409,10 @@ class MediaCarouselScrollHandler( scrollView.animationTargetX = newTranslation } else { // We're flinging the player! Let's go either to the previous or to the next player val pos = scrollView.scrollX val pos = scrollView.relativeScrollX val currentIndex = if (playerWidthPlusPadding > 0) pos / playerWidthPlusPadding else 0 var destIndex = if (vX <= 0) currentIndex + 1 else currentIndex val flungTowardEnd = if (isRtl) vX > 0 else vX < 0 var destIndex = if (flungTowardEnd) currentIndex + 1 else currentIndex destIndex = Math.max(0, destIndex) destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex) val view = mediaContent.getChildAt(destIndex) Loading Loading @@ -438,8 +454,14 @@ class MediaCarouselScrollHandler( activeMediaIndex = newIndex updatePlayerVisibilities() } val location = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0) val relativeLocation = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0) scrollInAmount.toFloat() / playerWidthPlusPadding else 0f // Fix the location, because PageIndicator does not handle RTL internally val location = if (isRtl) { mediaContent.childCount - relativeLocation - 1 } else { relativeLocation } pageIndicator.setLocation(location) updateClipToOutline() } Loading Loading @@ -480,13 +502,20 @@ class MediaCarouselScrollHandler( * where it was and update our scroll position. */ fun onPrePlayerRemoved(removed: MediaControlPanel) { val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= activeMediaIndex val removedIndex = mediaContent.indexOfChild(removed.view?.player) // If the removed index is less than the activeMediaIndex, then we need to decrement it. // RTL has no effect on this, because indices are always relative (start-to-end). // Update the index 'manually' since we won't always get a call to onMediaScrollingChanged val beforeActive = removedIndex <= activeMediaIndex 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) scrollView.scrollX = Math.max(scrollView.scrollX - playerWidthPlusPadding, 0) } // If the removed media item is "left of" the active one (in an absolute sense), we need to // scroll the view to keep that player in view. This is because scroll position is always // calculated from left to right. val leftOfActive = if (isRtl) !beforeActive else beforeActive if (leftOfActive) { scrollView.scrollX = Math.max(scrollView.scrollX - playerWidthPlusPadding, 0) } } Loading @@ -501,6 +530,13 @@ class MediaCarouselScrollHandler( } } /** * Reset the MediaScrollView to the start. */ fun scrollToStart() { scrollView.relativeScrollX = 0 } companion object { private val CONTENT_TRANSLATION = object : FloatPropertyCompat<MediaCarouselScrollHandler>( "contentTranslation") { Loading
packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt +38 −7 Original line number Diff line number Diff line Loading @@ -15,7 +15,10 @@ import com.android.systemui.util.animation.physicsAnimator * when only measuring children but not the parent, when trying to apply a new scroll position */ class MediaScrollView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : HorizontalScrollView(context, attrs, defStyleAttr) { lateinit var contentContainer: ViewGroup Loading @@ -37,6 +40,26 @@ class MediaScrollView @JvmOverloads constructor( contentContainer.translationX } /** * Convert between the absolute (left-to-right) and relative (start-to-end) scrollX of the media * carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX * is always absolute. This function is its own inverse. */ private fun transformScrollX(scrollX: Int): Int = if (isLayoutRtl) { contentContainer.width - width - scrollX } else { scrollX } /** * Get the layoutDirection-relative (start-to-end) scroll X position of the carousel. */ var relativeScrollX: Int get() = transformScrollX(scrollX) set(value) { scrollX = transformScrollX(value) } /** * Allow all scrolls to go through, use base implementation */ Loading @@ -55,15 +78,15 @@ class MediaScrollView @JvmOverloads constructor( } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { var intercept = false; var intercept = false touchListener?.let { intercept = it.onInterceptTouchEvent(ev) } return super.onInterceptTouchEvent(ev) || intercept; return super.onInterceptTouchEvent(ev) || intercept } override fun onTouchEvent(ev: MotionEvent?): Boolean { var touch = false; var touch = false touchListener?.let { touch = it.onTouchEvent(ev) } Loading @@ -75,9 +98,17 @@ class MediaScrollView @JvmOverloads constructor( contentContainer = getChildAt(0) as ViewGroup } override fun overScrollBy(deltaX: Int, deltaY: Int, scrollX: Int, scrollY: Int, scrollRangeX: Int, scrollRangeY: Int, maxOverScrollX: Int, maxOverScrollY: Int, isTouchEvent: Boolean): Boolean { override fun overScrollBy( deltaX: Int, deltaY: Int, scrollX: Int, scrollY: Int, scrollRangeX: Int, scrollRangeY: Int, maxOverScrollX: Int, maxOverScrollY: Int, isTouchEvent: Boolean ): Boolean { if (getContentTranslation() != 0.0f) { // When we're dismissing we ignore all the scrolling return false Loading