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


    Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

    Android Test Automation Engineer

    Komoot
    Remote
    • Full Time
    apply now

    Senior Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

, ,

From Scoped Storage to Photo Picker: Everything to know about Storage

Persistence is a core element of every mobile app. Android provides different APIs to access or expose files with different tradeoffs.
Watch Video

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer advocate
Google

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer ad ...
Google

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer advocat ...
Google

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

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu