Есть ли способ использовать функцию «картинка в картинке» для действия, которое не является видео, чтобы показать его в уменьшенном масштабе?
У меня есть активность с гигантским индикатором выполнения и некоторым текстом, который я хотел бы отображать в окне PiP, пока пользователь просматривал веб-страницы.
у меня уже есть
android:supportsPictureInPicture = "true"
android:configChanges = "screenSize|smallestScreenSize|screenLayout"
установить для активности в манифесте.
и запустить PiP
@Override
protected void onUserLeaveHint() {
PictureInPictureParams params = new PictureInPictureParams.Builder()
.build();
enterPictureInPictureMode(params);
}
Вот как выглядит мой пример приложения
Я нажимаю домой, и он ненадолго оживляется
а затем быстро перерисовывается, чтобы стать
Я надеюсь показать PiP в уменьшенном виде на картинке №2, но после быстрой анимации она перерисовывается так, как на картинке №3.
Есть ли способ добиться уменьшенного изображения?
Пожалуйста, имейте в виду, что это не приложение для магазина приложений. Это очень целевое приложение на специальном планшете.
Может быть, немного хакерский, но вы можете изменить DPI во время выполнения.
Следующий код использует onPictureInPictureModeChanged() для прослушивания изменения режима и изменения DPI при следующем перезапуске.
public class Activity extends AppCompatActivity {
private MyApplication mApplication;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mApplication = (MyApplication) getApplicationContext();
if (mApplication.mode == MyApplication.MODE_NONE) {
saveDpi();
} else {
setDpi();
}
setContentView(R.layout.activity);
...
}
private void saveDpi() {
Configuration configuration = getResources().getConfiguration();
mApplication.orgDensityDpi = configuration.densityDpi;
}
private void setDpi() {
Configuration configuration = getResources().getConfiguration();
DisplayMetrics metrics = getResources().getDisplayMetrics();
if (mApplication.mode == MyApplication.MODE_PIP) {
configuration.densityDpi = mApplication.orgDensityDpi / 3;
} else {
configuration.densityDpi = mApplication.orgDensityDpi;
}
getBaseContext().getResources().updateConfiguration(configuration, metrics);
}
@Override
protected void onUserLeaveHint() {
PictureInPictureParams params = new PictureInPictureParams.Builder().build();
enterPictureInPictureMode(params);
}
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
if (isInPictureInPictureMode) {
mApplication.mode = MyApplication.MODE_PIP;
} else {
mApplication.mode = MyApplication.MODE_FULL;
}
}
}
Поскольку onUserLeaveHint(), который запускает режим PIP, вызывается после onSaveInstanceState(), текущий режим не может быть сохранен в поле класса активности. Он должен храниться в другом месте, где он сохранится после изменения конфигурации. Здесь используется поле в классе приложения.
public class MyApplication extends Application {
public static final int MODE_NONE = 0;
public static final int MODE_FULL = 1;
public static final int MODE_PIP = 2;
public int mode = MODE_NONE;
public int orgDensityDpi = 0;
}
(Нет необходимости предотвращать изменения конфигурации с помощью android:configChanges.)
Результат:

Я пытался реализовать что-то подобное в kotlin, но не могу понять, как получить статические поля в основном классе приложения. Кто-нибудь может помочь с этим?
Нет необходимости включать configChanges в манифест.
public class PictureIPActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_picture_ip);
findViewById(R.id.tv_hello_world).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mApplication, "enabling PiP", Toast.LENGTH_SHORT).show();
enterPiPMode();
}
});
}
@Override
protected void onPause() {
super.onPause();
enterPiPMode();
}
private void enterPiPMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PictureInPictureParams params = new PictureInPictureParams.Builder()
.setAspectRatio(getPipRatio()).build();
enterPictureInPictureMode(params);
}
}
public Rational getPipRatio() {
// anything with aspect ration below 0.5 and above 2.5 (roughly) will be
unpractical or unpleasant to use
DisplayMetrics metrics = getResources().getDisplayMetrics();
return new Rational(Math.round(metrics.xdpi), Math.round(metrics.ydpi));
}
}
Ключом к изменению размера анимации являются настройки в AndroidManifest.xml.
<activity android:name = ".PictureIPActivity"
android:resizeableActivity = "true"
android:launchMode = "singleTask"
android:supportsPictureInPicture = "true">
Всякий раз, когда Activity входит в режим PIP или выходит из него, он уничтожается и создается заново (это поведение, которое я заметил). Разница между анимацией и конечным результатом заключается в том, что при входе в режим PIP система анимирует, уменьшая масштаб действия и его компонентов пользовательского интерфейса.
Когда действие воссоздается, оно использует тот же макет, который вы предоставили при первоначальном создании действия с теми же размерами, проблема в том, что конфигурация Activity изменилась, и устройство вошло в конфигурацию меньшего размера, т.е. в вашем случае от xlarge до small или normal.
Итак, теперь, когда мы знаем, что Activity уничтожен, вы можете обрабатывать изменения размера экрана, как обычно.
Вот что вы можете сделать:
onPictureInPictureModeChanged().Я добился желаемого результата, добавив новую папку dimens-small. Вы можете выбрать один для себя. Этот dimens.xml будет содержать android:textSize = "@dimen/textSize" для маленького экрана.
Теперь, когда это сделано здесь, вы, вероятно, не искали развлечений: согласно Документы PIP
specify that your activity handles layout configuration changes so that your activity doesn't relaunch when layout changes occur during PIP mode transitions.
И хотя я добавил
android:resizeableActivity = "true"
android:supportsPictureInPicture = "true"
android:configChanges = "screenSize|smallestScreenSize|screenLayout"
в моем теге <activity> в манифесте мой Activity все еще воссоздавался в конце каждого изменения режима.
Это либо ошибка, либо чего-то не хватает либо в документах, либо в коде. Или, возможно, неясное утверждение только для переходов/анимаций, а не для фактического конечного результата.
Вот еще одно решение, которое использует фрагменты для отображения масштабированного пользовательского интерфейса. В отличие от моего предыдущее решение, это решение имеет то преимущество, что позволяет отображать пользовательский интерфейс, оптимизированный для режима PIP. (Например, некоторые изображения могут быть скрыты в режиме PIP.)
Следующий код использует onPictureInPictureModeChanged() для прослушивания изменения режима и изменения пользовательского интерфейса при следующем перезапуске. (Поскольку панель инструментов не нужна в режиме PIP, она скрыта перед входом в режим PIP.)
public class Activity extends AppCompatActivity {
private static final String FRAGMENT_TAG_FULL = "fragment_full";
private static final String FRAGMENT_TAG_PIP = "fragment_pip";
private MyApplication mApplication;
private Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mApplication = (MyApplication) getApplicationContext();
setContentView(R.layout.activity);
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
if (!mApplication.inPipMode) {
showFullFragment();
} else {
showPipFragment();
}
}
@Override
protected void onUserLeaveHint() {
mToolbar.setVisibility(View.GONE);
PictureInPictureParams params = new PictureInPictureParams.Builder().build();
enterPictureInPictureMode(params);
}
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
if (isInPictureInPictureMode) {
mApplication.inPipMode = true;
} else {
mApplication.inPipMode = false;
}
}
private void showFullFragment() {
Fragment fragment = new FragmentFull();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container_content, fragment, FRAGMENT_TAG_FULL)
.commit();
mToolbar.setVisibility(View.VISIBLE);
}
private void showPipFragment() {
Fragment fragment = new FragmentPip();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container_content, fragment, FRAGMENT_TAG_PIP)
.commit();
mToolbar.setVisibility(View.GONE);
}
}
Поскольку onUserLeaveHint(), который запускает режим PIP, вызывается после onSaveInstanceState(), текущий режим не может быть сохранен в поле класса активности. Он должен храниться в другом месте, где он сохранится после изменения конфигурации. Здесь используется поле в классе приложения.
public class MyApplication extends Application {
public boolean inPipMode = false;
}
Макет фрагмента для полноэкранного режима:
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
android:layout_width = "match_parent"
android:layout_height = "match_parent">
<TextView
android:id = "@+id/text"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_centerHorizontal = "true"
android:layout_centerVertical = "true"
android:text = "Hello World!"
android:textSize = "36sp" />
<TextView
android:id = "@+id/text_detail"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_below = "@id/text"
android:layout_centerHorizontal = "true"
android:text = "🙂"
android:textSize = "28sp" />
</RelativeLayout>
Макет фрагмента для режима PIP:
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
android:layout_width = "match_parent"
android:layout_height = "match_parent">
<TextView
android:id = "@+id/text"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_centerHorizontal = "true"
android:layout_centerVertical = "true"
android:text = "Hello World!"
android:textSize = "10sp"/>
</RelativeLayout>
Результат:

этот ответ отлично работает, но только за счет воссоздания активности. было бы неплохо добиться этого без отдыха. нам нужно будет перерисовать/масштабировать макет без вызова set contentview