Масштабный вид «картинка в картинке» для Android

Есть ли способ использовать функцию «картинка в картинке» для действия, которое не является видео, чтобы показать его в уменьшенном масштабе?

У меня есть активность с гигантским индикатором выполнения и некоторым текстом, который я хотел бы отображать в окне PiP, пока пользователь просматривал веб-страницы.

у меня уже есть

android:supportsPictureInPicture = "true"
android:configChanges = "screenSize|smallestScreenSize|screenLayout"

установить для активности в манифесте.

и запустить PiP

@Override
protected void onUserLeaveHint() {


    PictureInPictureParams params = new PictureInPictureParams.Builder()
            .build();
    enterPictureInPictureMode(params);

}

Вот как выглядит мой пример приложения

Масштабный вид «картинка в картинке» для Android

Я нажимаю домой, и он ненадолго оживляется

Масштабный вид «картинка в картинке» для Android

а затем быстро перерисовывается, чтобы стать

Масштабный вид «картинка в картинке» для Android

Я надеюсь показать PiP в уменьшенном виде на картинке №2, но после быстрой анимации она перерисовывается так, как на картинке №3.

Есть ли способ добиться уменьшенного изображения?

Пожалуйста, имейте в виду, что это не приложение для магазина приложений. Это очень целевое приложение на специальном планшете.

10
0
5 570
4

Ответы 4

Может быть, немного хакерский, но вы можете изменить 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.)

Результат:

result

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

quealegriamasalegre 28.10.2021 19:31

Я пытался реализовать что-то подобное в kotlin, но не могу понять, как получить статические поля в основном классе приложения. Кто-нибудь может помочь с этим?

WKGuy 07.11.2021 05:17

Нет необходимости включать 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 уничтожен, вы можете обрабатывать изменения размера экрана, как обычно.

Вот что вы можете сделать:

  1. Предоставьте новый макет для новой конфигурации.
  2. Укажите новый размер текста для новых конфигураций.
  3. Укажите новый размер текста во время выполнения при обратном вызове 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 = "&#x1F642;"
        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>

Результат:

result

Другие вопросы по теме