将 Android WindowBackground 与 Activity ImageView 居中

Center Android WindowBackground with Activity ImageView

上下文

第 1 步: 目前我有一个 activity 在我们等待 activity 加载时使用 android:windowBackground 设置初始背景.这将加载一个应该居中对齐的位图。

第 2 步: 加载 activity 后,它会执行简单的 setContentView 设置 activity 的背景,现在将替换来自 step1android:windowBackground。这会加载一个应该居中对齐的 imageView

问题是它们不是都居中对齐,一个或另一个上有某种偏移使它们不对齐。也许状态栏会向下推那个?我不知道。知道为什么它们不对齐吗?

我想让它们都居中对齐。我试过使用 fitSystemWindows="true" 但没有成功。

当我将 android:layout_marginTop="12dp" 添加到 activity (activity_start_onboarding.xml) 布局时 imageView 都可以很好地对齐,但并非所有密度都对齐,其他密度未对齐。

也许有一种方法可以动态计算此偏移量以对齐所有密度?


比较图片

左(灰色)- Step1 (windowBackground):
右(蓝色)- 第 2 步(activity 布局):


代码

AndroidManifest.xml

<activity android:name=".view.onboarding.ActivityStartOnboarding"
            android:launchMode="singleTask"
            android:screenOrientation="portrait"
            android:theme="@style/ColdstartSplashTheme">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

styles.xml

<style name="ColdstartSplashTheme" parent="SuperbalistTheme.Dark">
    <item name="android:windowBackground">@drawable/splash_background</item>
</style>

@drawable/splash_background

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/grey_mid_dark" />

<item>
    <bitmap
        android:gravity="center"
        android:src="@drawable/ic_delivery_free_raster" />
</item>

ActivityStartOnboarding.java

public class ActivityStartOnboarding extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_start_onboarding);
  }
}

activity_start_onboarding.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/blue_light_darker"
        android:fitsSystemWindows="true">

        <androidx.appcompat.widget.AppCompatImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:src="@drawable/splash_background" />
    </FrameLayout>
</layout>
android:fitsSystemWindows="true"

那是你的问题。您的 FrameLayout 开始在状态栏下膨胀,高度约为 25dp,因此 AppCompatImageView 高一点。

第一个解决方案: 从 activity_start_onboarding.xml FrameLayout 中删除 android:fitsSystemWindows="true"

第二种方案: 将 <item name="android:windowDrawsSystemBarBackgrounds">true</item> 添加到您的 ColdstartSplashTheme。背景在状态栏下开始绘制,位图会更高

使用背景代替窗口背景

<style name="ColdstartSplashTheme" parent="SuperbalistTheme.Dark">
    <item name="android:background">@drawable/splash_background</item>
</style>

对我来说,接受的解决方案没有用。但我找到了 this,它就像一个魅力。 它也是一个更灵活的解决方案,因为它不依赖于 API 21

None 其他解决方案无论出于何种原因都对我有用。所以,我写了一个类似于 ImageView 的快速 class,不同之处在于 它相对于整个 window 而不是在其自己的视图范围内居中它给出的可绘制对象.

基本上 windowBackground 在您的主题中使用 window 的边界。你的 ImageView,即使它填满了它的父级,仍然受到你的 activity 边界的限制,这是一个不同的形状(例如不包括系统酒吧)。因此,在 window 中将可绘制对象居中(windowBackground 的作用)与在 view 中将可绘制对象居中会产生不同的结果(ImageView 的作用)。我写的 View class 将可绘制对象置于 window 的中心,所以它与您的主题做同样的事情。

这是class:

package your.package.name

import android.app.Activity
import android.content.Context
import android.graphics.Canvas
import android.graphics.Point
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import your.package.name.R

/**
 * Simply draws the given drawable in the center of the current window with no scaling. This is not
 * very sophisticated, so cannot handle a lot of the features that a regular [ImageView] can. If
 * these features are required, you might want to try subclassing [ImageView].
 */
class WindowCenteredImageView(context: Context, attrs: AttributeSet): View(context, attrs) {

    // Obtain drawable from attributes.
    private val d: Drawable?
    init {
        context.theme.obtainStyledAttributes(
            attrs,
            R.styleable.WindowCenteredImageView,
            0, 0
        ).apply {
            try {
                d = getDrawable(R.styleable.WindowCenteredImageView_src)
            } finally {
                recycle()
            }
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        if (d != null) {
            // We have a drawable, so try to draw it!
            if (d.intrinsicWidth >= 0 && d.intrinsicHeight >= 0) {
                // Our drawable doesn't fill the screen, so we should calculate its bounds. This is
                // where we take into account the current view's offset within the window...
                val viewTopLeftRelativeToWindow = onDrawCachedObjects.point1.apply {
                    val array = onDrawCachedObjects.array.apply { getLocationInWindow(this) }
                    set(array[0], array[1])
                }

                // And the window's dimensions...
                // (Casting here can't fail as views are always given activity context.)
                val window = (context as Activity).window.decorView
                val windowHeight = window.height
                val windowWidth = window.width

                // Do the calculations.
                // First, find out where we want the image to go in the window
                val imageTopLeftRelativeToWindow = onDrawCachedObjects.point2.apply {
                    set(
                        (windowWidth / 2) - (d.intrinsicWidth / 2),
                        (windowHeight / 2) - (d.intrinsicHeight / 2)
                    )
                }

                // Now we can calculate where the image should go in the view
                val imageTopLeftRelativeToView = onDrawCachedObjects.point3.apply {
                    set(
                        imageTopLeftRelativeToWindow.x - viewTopLeftRelativeToWindow.x,
                        imageTopLeftRelativeToWindow.y - viewTopLeftRelativeToWindow.y
                    )
                }

                // We have all the information needed to set the image bounds
                d.setBounds(
                    imageTopLeftRelativeToView.x,
                    imageTopLeftRelativeToView.y,
                    imageTopLeftRelativeToView.x + d.intrinsicWidth,
                    imageTopLeftRelativeToView.y + d.intrinsicHeight
                )
            }

            // Draw the drawable onto the canvas
            d.draw(canvas)
        }
    }

    /** Optimization: Objects to use in [onDraw] to avoid instantiations */
    private val onDrawCachedObjects = object {
        val array = IntArray(2)
        val point1 = Point()
        val point2 = Point()
        val point3 = Point()
    }
}

使用方法如下:

  • 首先你需要把这个放在你的 res/values/attrs.xml:
    <declare-styleable name="WindowCenteredImageView">
        <!-- Sets a drawable as the content of this ImageView. -->
        <attr name="src" format="reference" />
    </declare-styleable>
  • 现在用这个替换你的 ImageView(它需要比它要绘制的可绘制对象大,因为包装内容可能无法正常工作:我只是让这个视图成为 activity 使用 match_parent - 在你给它的 drawable 之外,它将是透明的):
<your.package.name.WindowCenteredImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:src="@drawable/ic_delivery_free_raster"/>