Android画中画缩放视图

10

有没有办法在不是视频的活动上使用画中画功能来显示缩小的内容?

我有一个带有巨大进度条和一些文字的活动,我想在用户进行网页浏览时在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张图片的样子。

有没有办法实现缩小视图?

请记住,这不是一个将要上架应用商店的应用程序。它是专用平板电脑上非常具有针对性的应用程序。

4个回答

6
也许有点取巧,但你可以在运行时更改 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;
        }
    }

}

由于启动 PIP 模式的 onUserLeaveHint() 方法是在调用 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


这个答案非常有效,但是需要重新创建 Activity 才能实现。不使用重新创建 Activity 来重新绘制/缩放布局并调用 setContentView 将会更好。 - quealegriamasalegre
我正在尝试在Kotlin中实现类似的东西,但我无法弄清楚如何在主应用程序类上获取静态字段。有人可以帮忙吗? - WKGuy

4
这里有另一种解决方案,它使用片段来展示缩放的UI。与我之前的解决方案不同,这个解决方案的优点在于能够展示为PIP模式优化的UI。(例如,在PIP模式下可以隐藏某些视图。)
以下代码使用onPictureInPictureModeChanged()监听模式变化,并在下次重启时更改UI。(因为在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() 方法会在 onSaveInstanceState() 方法之后调用,这将启动 PIP 模式,因此当前模式无法存储在 activity 类的字段中。它必须存储在其他地方,以便在配置更改时保留。此处使用应用程序类中的字段。
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


进入 PIP 模式时,onCreate 方法没有被调用... 我错了吗? - Barel elbaz
进入 PIP 模式会导致配置更改。默认情况下,应用程序会重新创建并调用 "onCreate"。但是,可以在应用清单文件中禁止此操作。请参阅:https://developer.android.com/guide/topics/ui/picture-in-picture#declaring - Matt Ke
好的,明白了。所以如果我在清单文件中写入“处理”配置更改,它就不会再次调用onCreate方法了。 - Barel elbaz

3
不需要在清单文件中包含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">

1
每当Activity进入或退出PIP模式时,它都会被销毁并重新创建(这是我注意到的行为)。动画和最终结果之间的区别在于,当进入PIP模式时,系统通过缩小活动及其UI组件来进行动画处理。
当活动被重新创建时,它将使用您在活动初始创建期间提供的相同布局和相同的dimens。问题在于,Activity的配置已更改,并且设备已进入较小的尺寸配置,例如从xlarge到small或normal。
所以现在我们知道Activity已被销毁,您可以像通常一样处理屏幕大小更改。
以下是您可以执行的操作:
  1. 为新配置提供新的布局。
  2. 为新配置提供新的文本大小。
  3. onPictureInPictureModeChanged()回调上提供新的文本大小。
我通过添加一个新的dimens-small文件夹来实现所需的结果。您可以为自己选择一个。此dimens.xml将包含适用于小屏幕的android:textSize="@dimen/textSize"
现在我们完成了,以下是你可能没有寻找娱乐的原因:根据PIP文档,指定您的活动处理布局配置更改,以便在PIP模式转换期间布局更改发生时,您的活动不会重新启动。即使我已经添加了。
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout"

在我的清单文件中的<activity>标签中,每次模式更改结束时我的Activity仍然会被重新创建。这可能是一个错误,或者文档或代码中缺少某些内容。或者只是针对过渡/动画而不是实际结果的不明确说明。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接