Merge "Paged carousel for media players" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
83dcd5f76f
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user