Android画中画比例视图

Posted

技术标签:

【中文标题】Android画中画比例视图【英文标题】:Android picture-in-picture scale view 【发布时间】:2019-07-13 11:23:20 【问题描述】:

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

我有一个带有巨大进度条和一些文本的活动,我想在用户浏览网页时在画中画窗口上显示它们。

我已经有了

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

为清单中的活动设置。

开始画中画

@Override
protected void onUserLeaveHint() 


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


这就是我的示例应用的样子

我按下 home 键,它会短暂地动画到

然后快速重绘成为

我希望显示画中画,因为它在图片 #2 中按比例缩小,但在快速动画之后,它会重新绘制到图片 #3 中的样子。

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

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

【问题讨论】:

【参考方案1】:

可能有点老套,但您可以在运行时更改 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() - 启动画中画模式 - 在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 阻止配置更改。)

结果:

【讨论】:

这个答案效果很好,但只是以重新创建活动为代价。如果没有娱乐就可以做到这一点会很好。我们需要重绘/缩放布局而不调用 set contentview 我试图在 koitlin 中实现类似的东西,但我不知道如何在主应用程序类上获取静态字段。有人可以帮忙吗?【参考方案2】:

这是另一个使用片段显示缩放 UI 的解决方案。与我的previous solution 不同,此解决方案的优势在于能够显示针对 PIP 模式优化的 UI。 (例如,某些视图可以在 PIP 模式下隐藏。)

以下代码使用 onPictureInPictureModeChanged() 侦听模式更改并在下次重新启动时更改 UI。 (因为在画中画模式下不需要工具栏,所以在进入画中画模式之前它是隐藏的。)

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_
    android:layout_>

    <TextView
        android:id="@+id/text"
        android:layout_
        android:layout_
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Hello World!"
        android:textSize="36sp" />

    <TextView
        android:id="@+id/text_detail"
        android:layout_
        android:layout_
        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_
    android:layout_>

    <TextView
        android:id="@+id/text"
        android:layout_
        android:layout_
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Hello World!"
        android:textSize="10sp"/>

</RelativeLayout>

结果:

【讨论】:

【参考方案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">

【讨论】:

【参考方案4】:

每当Activity 进入或退出 PIP 模式时,它都会被销毁并重新创建(这是我已经注意到的行为)。动画和最终结果的区别在于,当进入 PIP 模式时,系统会通过缩小 Activity 及其 UI 组件来进行动画处理。

重新创建活动时,它使用您在初始创建活动时提供的相同布局,具有相同的尺寸,问题是Activity 的配置已更改并且设备已进入更小的尺寸配置,即在您的情况下从 xlarge 到 small 或 normal。

既然我们知道Activity 已被销毁,您就可以像往常一样处理屏幕尺寸的变化了。

您可以这样做:

    为新配置提供新布局。 为新配置提供新的文本大小。 在运行时在 onPictureInPictureModeChanged() 回调上提供新的文本大小。

我通过添加一个新的dimens-small 文件夹达到了预期的效果。你可以为自己选择一个。这个dimens.xml 将包含小屏幕的android:textSize="@dimen/textSize"


既然这里已经完成,这就是你可能没有寻找娱乐的原因:根据PIP Docs

指定您的 Activity 处理布局配置更改,以便在 PIP 模式转换期间发生布局更改时您的 Activity 不会重新启动。

即使我添加了

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

在清单中我的&lt;activity&gt; 标记中,我的Activity 在每次模式更改结束时仍会重新创建。

这要么是错误,要么是文档或代码中缺少的东西。或者可能只是对过渡/动画的不清楚的陈述,而不是实际的最终结果。

【讨论】:

以上是关于Android画中画比例视图的主要内容,如果未能解决你的问题,请参考以下文章

Android 7.0都有哪些新功能 Android 7.0新特性汇总

findViewById 上的错误-二次采样比例图像视图-Android [关闭]

Android画中画出现在黑色方块上

在 Android 手机(而非 Android TV)上使用画中画功能

Android N画中画模式

Android N画中画模式