Merge "Paged carousel for media players" into rvc-dev

This commit is contained in:
Robert Snoeberger
2020-05-22 15:34:27 +00:00
committed by Android (Google) Code Review
4 changed files with 125 additions and 19 deletions

View File

@@ -16,22 +16,39 @@
-->
<!-- Carousel for media controls -->
<com.android.systemui.media.UnboundHorizontalScrollView
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:clipChildren="false"
android:clipToPadding="false"
>
<LinearLayout
android:id="@+id/media_carousel"
android:layout_width="wrap_content"
<com.android.systemui.media.UnboundHorizontalScrollView
android:id="@+id/media_carousel_scroller"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:scrollbars="none"
android:clipChildren="false"
android:clipToPadding="false"
>
<!-- QSMediaPlayers will be added here dynamically -->
</LinearLayout>
</com.android.systemui.media.UnboundHorizontalScrollView>
<LinearLayout
android:id="@+id/media_carousel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:clipChildren="false"
android:clipToPadding="false"
>
<!-- QSMediaPlayers will be added here dynamically -->
</LinearLayout>
</com.android.systemui.media.UnboundHorizontalScrollView>
<com.android.systemui.qs.PageIndicator
android:id="@+id/media_page_indicator"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="4dp"
android:layout_gravity="center_horizontal|bottom"
android:gravity="center"
android:tint="@color/media_primary_text"
/>
</FrameLayout>

View File

@@ -58,7 +58,7 @@ class MediaHierarchyManager @Inject constructor(
private var rootOverlay: ViewGroupOverlay? = null
private lateinit var currentState: MediaState
private val mediaCarousel
get() = mediaViewManager.mediaCarousel
get() = mediaViewManager.mediaFrame
private var animationStartState: MediaState? = null
private var statusbarState: Int = statusBarStateController.state
private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
@@ -424,4 +424,4 @@ class MediaHierarchyManager @Inject constructor(
*/
const val IN_OVERLAY = -1000
}
}
}

View File

@@ -1,15 +1,20 @@
package com.android.systemui.media
import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import androidx.core.view.GestureDetectorCompat
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.PageIndicator
import com.android.systemui.statusbar.notification.VisualStabilityManager
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.UniqueObjectHostView
@@ -18,6 +23,8 @@ import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Singleton
private const val FLING_SLOP = 1000000
/**
* Class that is responsible for keeping the view carousel up to date.
* This also handles changes in state and applies them to the media carousel like the expansion.
@@ -35,9 +42,12 @@ class MediaViewManager @Inject constructor(
private var playerWidthPlusPadding: Int = 0
private var desiredState: MediaHost.MediaHostState? = null
private var currentState: MediaState? = null
val mediaCarousel: HorizontalScrollView
private val mediaCarousel: HorizontalScrollView
val mediaFrame: ViewGroup
private val mediaContent: ViewGroup
private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
private val pageIndicator: PageIndicator
private val gestureDetector: GestureDetectorCompat
private val visualStabilityCallback: VisualStabilityManager.Callback
private var activeMediaIndex: Int = 0
private var needsReordering: Boolean = false
@@ -66,10 +76,30 @@ class MediaViewManager @Inject constructor(
scrollX % playerWidthPlusPadding)
}
}
private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
override fun onFling(
eStart: MotionEvent?,
eCurrent: MotionEvent?,
vX: Float,
vY: Float
): Boolean {
return this@MediaViewManager.onFling(eStart, eCurrent, vX, vY)
}
}
private val touchListener = object : View.OnTouchListener {
override fun onTouch(view: View, motionEvent: MotionEvent?): Boolean {
return this@MediaViewManager.onTouch(view, motionEvent)
}
}
init {
mediaCarousel = inflateMediaCarousel()
gestureDetector = GestureDetectorCompat(context, gestureListener)
mediaFrame = inflateMediaCarousel()
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
mediaCarousel.setOnScrollChangeListener(scrollChangedListener)
mediaCarousel.setOnTouchListener(touchListener)
mediaCarousel.setOverScrollMode(View.OVER_SCROLL_NEVER)
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
visualStabilityCallback = VisualStabilityManager.Callback {
if (needsReordering) {
@@ -103,14 +133,15 @@ class MediaViewManager @Inject constructor(
playerWidthPlusPadding, 0)
}
updatePlayerVisibilities()
updatePageIndicator()
}
}
})
}
private fun inflateMediaCarousel(): HorizontalScrollView {
private fun inflateMediaCarousel(): ViewGroup {
return LayoutInflater.from(context).inflate(R.layout.media_carousel,
UniqueObjectHostView(context), false) as HorizontalScrollView
UniqueObjectHostView(context), false) as ViewGroup
}
private fun reorderAllPlayers() {
@@ -133,6 +164,47 @@ class MediaViewManager @Inject constructor(
activeMediaIndex = newIndex
updatePlayerVisibilities()
}
val location = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
scrollInAmount.toFloat() / playerWidthPlusPadding else 0f
pageIndicator.setLocation(location)
}
private fun onTouch(view: View, motionEvent: MotionEvent?): Boolean {
if (gestureDetector.onTouchEvent(motionEvent)) {
return true
}
if (motionEvent?.getAction() == MotionEvent.ACTION_UP) {
val pos = mediaCarousel.scrollX % playerWidthPlusPadding
if (pos > playerWidthPlusPadding / 2) {
mediaCarousel.smoothScrollBy(playerWidthPlusPadding - pos, 0)
} else {
mediaCarousel.smoothScrollBy(-1 * pos, 0)
}
return true
}
return view.onTouchEvent(motionEvent)
}
private fun onFling(
eStart: MotionEvent?,
eCurrent: MotionEvent?,
vX: Float,
vY: Float
): Boolean {
if (vX * vX < 0.5 * vY * vY) {
return false
}
if (vX * vX < FLING_SLOP) {
return false
}
val pos = mediaCarousel.scrollX
val currentIndex = if (playerWidthPlusPadding > 0) pos / playerWidthPlusPadding else 0
var destIndex = if (vX <= 0) currentIndex + 1 else currentIndex
destIndex = Math.max(0, destIndex)
destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
val view = mediaContent.getChildAt(destIndex)
mediaCarousel.smoothScrollTo(view.left, mediaCarousel.scrollY)
return true
}
private fun updatePlayerVisibilities() {
@@ -176,6 +248,7 @@ class MediaViewManager @Inject constructor(
// motion model
existingPlayer.view?.player?.progress = currentState?.expansion ?: 0.0f
updateMediaPaddings()
updatePageIndicator()
}
private fun updatePlayerToCurrentState(existingPlayer: MediaControlPanel) {
@@ -199,6 +272,14 @@ class MediaViewManager @Inject constructor(
}
}
private fun updatePageIndicator() {
val numPages = mediaContent.getChildCount()
pageIndicator.setNumPages(numPages, Color.WHITE)
if (numPages == 1) {
pageIndicator.setLocation(0f)
}
}
/**
* Set the current state of a view. This is updated often during animations and we shouldn't
* do anything expensive.
@@ -206,6 +287,9 @@ class MediaViewManager @Inject constructor(
fun setCurrentState(state: MediaState) {
currentState = state
currentlyExpanded = state.expansion > 0
// Hack: Since the indicator doesn't move with the player expansion, just make it disappear
// and then reappear at the end.
pageIndicator.alpha = if (state.expansion == 1f || state.expansion == 0f) 1f else 0f
for (mediaPlayer in mediaPlayers.values) {
val view = mediaPlayer.view?.player
view?.progress = state.expansion

View File

@@ -45,6 +45,15 @@ public class PageIndicator extends ViewGroup {
}
public void setNumPages(int numPages) {
TypedArray array = getContext().obtainStyledAttributes(
new int[]{android.R.attr.colorControlActivated});
int color = array.getColor(0, 0);
array.recycle();
setNumPages(numPages, color);
}
/** Oveload of setNumPages that allows the indicator color to be specified.*/
public void setNumPages(int numPages, int color) {
setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
if (mAnimating) {
Log.w(TAG, "setNumPages during animation");
@@ -52,10 +61,6 @@ public class PageIndicator extends ViewGroup {
while (numPages < getChildCount()) {
removeViewAt(getChildCount() - 1);
}
TypedArray array = getContext().obtainStyledAttributes(
new int[]{android.R.attr.colorControlActivated});
int color = array.getColor(0, 0);
array.recycle();
while (numPages > getChildCount()) {
ImageView v = new ImageView(mContext);
v.setImageResource(R.drawable.minor_a_b);