Blog Infos
Author
Published
Topics
Published
Topics
We can add Pip Support in a few steps by creating a simple time-showing app:
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:supportsPictureInPicture="true"

Step 3: Create the layout of your activity.

<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/stopwatch_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/red"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0" />
<ImageButton
android:id="@+id/start_or_pause"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:layout_margin="@dimen/spacing_medium"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_play_arrow_24dp"
app:layout_constraintBottom_toBottomOf="@id/stopwatch_background"
app:layout_constraintEnd_toEndOf="@id/stopwatch_background"
app:layout_constraintTop_toTopOf="@id/stopwatch_background"
app:tint="@android:color/white" />
<TextView
android:id="@+id/time"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="@dimen/spacing_medium"
android:autoSizeTextType="uniform"
android:gravity="center"
android:maxLines="1"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="@id/stopwatch_background"
app:layout_constraintEnd_toStartOf="@id/start_or_pause"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/stopwatch_background"
tools:text="00:00:00:00" />
</androidx.constraintlayout.widget.ConstraintLayout>

Step 4: Now, we have to create a PictureInPictureParams builder to initialize and update activity in Pip Mode.

private fun updatePictureInPictureParams(started: Boolean): PictureInPictureParams {
val visibleRect = Rect()
binding.timerBackground.getGlobalVisibleRect(visibleRect)
val params = PictureInPictureParams.Builder()
.setActions(
listOf(
if (started) {
createRemoteAction(
R.drawable.ic_pause_24dp,
R.string.pause,
START_OR_PAUSE_TIME,
CONTROL_TYPE_START_OR_PAUSE
)
} else {
createRemoteAction(
R.drawable.ic_play_arrow_24dp,
R.string.start,
START_OR_PAUSE_TIME,
CONTROL_TYPE_START_OR_PAUSE
)
}
)
)
.setAspectRatio(Rational(16, 9))
.setSourceRectHint(visibleRect)
.setAutoEnterEnabled(true)
.setSeamlessResizeEnabled(false)
.build()
setPictureInPictureParams(params)
return params
}
view raw MainActivity.kt hosted with ❤ by GitHub

Now depending on whether the timer has started or not we change our RemoteAction and we have to handle actions using a broadcast receiver. and to enter pip mode in Android 12 and above add this

.setAutoEnterEnabled(true)
private val broadcastReceiver = object : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent == null || intent.action != ACTION_TIMER_CONTROL) {
            return
        }
        when (intent.getIntExtra(CONTROL_TYPE, 0)) {
            CONTROL_TYPE_START_OR_PAUSE -> viewModel.startOrPause()
        }
    }
}
override fun onPictureInPictureModeChanged(
    isInPictureInPictureMode: Boolean,
    newConfig: Configuration
) {
    super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
    if (isInPictureInPictureMode) {
        binding.startOrPause.visibility = View.GONE
    } else {
        binding.startOrPause.visibility = View.VISIBLE
    }
}

Step 7: Now for Android 11 and below you need to enable pip mode when the user leaves and goes to the background.

override fun onUserLeaveHint() {
    super.onUserLeaveHint()
    viewModel.started.observe(this) { started ->
        binding.startOrPause.setImageResource(
            if (started) R.drawable.ic_pause_24dp else R.drawable.ic_play_arrow_24dp
        )
        enterPictureInPictureMode(updatePictureInPictureParams(started))
    }
}
class MainViewModel: ViewModel() {
private var job: Job? = null
private val timeMillis = MutableLiveData("00:00:00")
private val _started = MutableLiveData(false)
val started: LiveData<Boolean> = _started
val time = timeMillis
private val sdf = SimpleDateFormat("hh:mm:ss:SS", Locale.US)
fun startOrPause() {
if (_started.value == true) {
_started.value = false
job?.cancel()
} else {
_started.value = true
job = viewModelScope.launch { start() }
}
}
private suspend fun CoroutineScope.start() {
while (isActive) {
timeMillis.value = sdf.format(Date())
awaitFrame()
}
}
}

Here we simply create a job and cancel or start depending on the user’s choice.

Thank You For Reading

This article was originally published on proandroiddev.com

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
This is the second article in an article series that will discuss the dependency…
READ MORE
blog
Let’s suppose that for some reason we are interested in doing some tests with…
READ MORE
Menu