如何拥有不调整大小的全屏背景图像(带中心裁剪)

Posted

技术标签:

【中文标题】如何拥有不调整大小的全屏背景图像(带中心裁剪)【英文标题】:How to have a Fullscreen Background Image (with center-crop) that doesn't Resize 【发布时间】:2014-03-15 19:48:31 【问题描述】:

我正在尝试将照片设置为Activity 的背景。我希望背景是a full screen image (no borders)。

因为我希望图像填充整个活动的背景而不拉伸/挤压(即 X 和 Y 的不成比例缩放),并且我不介意是否必须裁剪照片,所以我使用 RelativeLayout一个ImageView(和android:scaleType="centerCrop")和我的布局的其余部分由一个ScrollView 和它的孩子组成。

<!-- Tried this with a FrameLayout as well... -->
<RelativeLayout> 
   <!-- Background -->
   <ImageView  
      android:layout_
      android:layout_
      android:scaleType="centerCrop"/>
   <!-- Form -->
   <LinearLayout
      android:layout_
      android:layout_>
      <ScrollView>
        <LinearLayout>
          ...
          <EditText/>
        </LinearLayout>
      </ScrollView>
   </LinearLayout>
</RelativeLayout>

问题是布局的其余部分有一些EditText 视图,当软键盘出现时,ImageView 会重新调整大小。无论软键盘是否可见,我都希望背景保持不变。

我在 SO 上看到 plenty of questions 关于 ImageViews 正在重新调整大小但(imo)没有令人满意的答案。它们中的大多数只包括设置android:windowSoftInputMode="adjustPan" - 这并不总是实用的,特别是如果您希望用户能够在活动中滚动 - 或者使用不裁剪图像的getWindow().setBackgroundDrawable()

我已经设法继承 ImageView 并覆盖其onMeasure()(请参阅我的答案:ImageView resizes when keyboard open),以便它可以强制固定高度和宽度 - 等于设备的屏幕尺寸 - 根据标志但我想知道是否有更好的方法来实现我想要的结果。

所以,总而言之,我的问题是:如何将 Activity 的背景设置为全屏照片

使用 scale type = "centerCrop",使照片均匀缩放(保持其纵横比),因此两个尺寸(宽度和高度)将等于或大于相应尺寸视图;

在弹出软键盘时不会调整大小;


回答:

我最终遵循了@pskink 的建议并将BitmapDrawable 子类化(请参阅下面的答案)。我必须进行一些调整以确保 BackgroundBitmapDrawable 始终以填满屏幕的方式进行缩放和裁剪。

这是我的最后一堂课,改编自他的回答:

public class BackgroundBitmapDrawable extends BitmapDrawable 
    private Matrix mMatrix = new Matrix();
    private int moldHeight;
    boolean simpleMapping = false;

    public BackgroundBitmapDrawable(Resources res, Bitmap bitmap) 
        super(res, bitmap);
    

    @Override
    protected void onBoundsChange(Rect bounds) 
        if (bounds.height() > moldHeight) 
            moldHeight = bounds.height();
            Bitmap b = getBitmap();
            RectF src = new RectF(0, 0, b.getWidth(), b.getHeight());
            RectF dst;

            if (simpleMapping) 
                dst = new RectF(bounds);
                mMatrix.setRectToRect(src, dst, ScaleToFit.CENTER);
             else 
                // Full Screen Image -> Always scale and center-crop in order to fill the screen
                float dwidth = src.width();
                float dheight = src.height();

                float vwidth = bounds.width(); 
                float vheight = bounds.height();

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) 
                    scale = (float) vheight / (float) dheight; 
                    dx = (vwidth - dwidth * scale) * 0.5f;
                 else 
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                

                mMatrix.setScale(scale, scale);
                mMatrix.postTranslate(dx, dy);

            
        
    

    @Override
    public void draw(Canvas canvas) 
        canvas.drawColor(0xaa00ff00);
        canvas.drawBitmap(getBitmap(), mMatrix, null);
    

然后只需创建一个BackgroundBitmapDrawable 并将其设置为根视图的背景即可。

【问题讨论】:

你能发布你的 XML 吗?这听起来像您希望活动的背景不缩放(这可能是相对布局中的 imageView 和包含其他元素的滚动视图),但是您正在覆盖 imageview,然后提到 EditText。您的 XML 可能有助于澄清问题。 @Jim 我已经发布了一些 XML。覆盖 ImageView 只是我考虑过的一种可能的解决方案,但如果可以的话,我宁愿避免它。 不要使用 ImageView,使用适当的 BitmapDrawable(顶部或中心重心)作为布局背景 我假设你已经尝试了这里的所有东西(我认为“isScrollContainter”解决了我的问题,但我现在找不到它):***.com/questions/4207880/… 太棒了。这是最好的解决方案,下面贴一些代码供大家使用。 【参考方案1】:

将所有内容放在 FrameLayout 中,第一个子项是一个与其父级(frameLayout)匹配的图像视图,您可以根据需要对其进行配置,然后在图像视图前面放置您想要的任何内容(LinearLayout I假设)

【讨论】:

这正是我的问题。我已经尝试过使用 FrameLayout 和 RelativeLayout。问题是当软键盘出现时,后台的 ImageView 会重新调整大小。即使 ImageView 保持它的比例,我更喜欢不管键盘是隐藏还是显示它都保持相同的外观。 甚至将其配置为裁剪? 是的,这是我尝试的第一件事:带有 ImageView (centerCrop) 的 Frame/RelativeLayout。显示键盘时,图像会重新调整大小。查看接受的答案+我的编辑:)【参考方案2】:

试试这个 LayerDrawable (res/drawable/backlayer.xml):

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
>
    <item>
        <shape>
        <solid android:color="#f00" />
        </shape>
    </item>
<item>
        <bitmap
        android:gravity="top" android:src="@drawable/back1">
        </bitmap>
    </item>
</layer-list>

并将其设置为您的***布局:android:background="@drawable/backlayer"

更新:试试这个 BitmapDrawadle,将其设置为***布局 (setBackgroundDrawable()),如果 simpleMapping == true 足够好,您可以删除“else”分支:

class D extends BitmapDrawable 
    private Matrix mMatrix = new Matrix();
    private int moldHeight;

    public D(Resources res, Bitmap bitmap) 
        super(res, bitmap);
    

    @Override
    protected void onBoundsChange(Rect bounds) 
        if (bounds.height() > moldHeight) 
            moldHeight = bounds.height();
            Bitmap b = getBitmap();
            RectF src = new RectF(0, 0, b.getWidth(), b.getHeight());
            RectF dst;

            // if simpleMapping is good enough then remove "else" branch and
            // declare "dst" as:
            // RectF dst = new RectF(bounds);
            boolean simpleMapping = true;
            if (simpleMapping) 
                dst = new RectF(bounds);
             else 
                float x = bounds.exactCenterX();
                dst = new RectF(x, 0, x, bounds.height());
                float scale = bounds.height() / src.height();
                float dx = scale * src.width() / 2;
                dst.inset(-dx, 0);
            
            mMatrix.setRectToRect(src, dst, ScaleToFit.CENTER);
        
    

    @Override
    public void draw(Canvas canvas) 
        canvas.drawColor(0xaa00ff00);
        canvas.drawBitmap(getBitmap(), mMatrix, null);
    

【讨论】:

这并不能保证整个视口都会像***.com/questions/16135984/… 中那样被图像填充。此解决方案是否允许图像仅填充(例如)屏幕的一半而屏幕的其余部分由纯色填充,具体取决于方向、屏幕密度、大小等?这不是我想要达到的目标。请参阅此评论中的链接。 @user1987392 你说没有“拉伸/挤压”,所以它根本没有缩放,现在你说如果屏幕大于图像然后进行图像缩放? 好吧,也许我的话没有明智地选择:通过拉伸/挤压,我的意思是图像的高度和宽度没有以相同的比例重新调整大小。这显然会使照片看起来很奇怪。我确实想要缩放,但我希望它是“中心裁剪”。【参考方案3】:

在摸索了一段时间后,这是我的“暂时不错”的解决方案。

分享一个对任何人都可能有用的示例 XML。

<!-- RelativeLayout so that Views can overlap each other.
     I believe a FrameLayout would work as well. -->
<RelativeLayout
    android:id="@+id/root"
    android:layout_
    android:layout_ >

    <!-- I've put the ImageView inside 
         a ScrollView with android:fillViewport="true" and android:isScrollContainer="false" -->
    <ScrollView
        android:id="@+id/backgroundScrollView"
        android:layout_
        android:layout_
        android:fillViewport="true"
        android:isScrollContainer="false" > 

        <LinearLayout
            android:layout_
            android:layout_
            android:orientation="vertical" >

            <!-- The FullScreen, Center-Cropped Image Background --> 
            <ImageView
                android:id="@+id/backgroundImage"
                android:layout_
                android:layout_
                android:scaleType="centerCrop"
                android:src="@drawable/background_image" />
        </LinearLayout>
    </ScrollView>

    <!-- The rest of the Views, containing the data entry form... -->
    <LinearLayout
        android:id="@+id/form"
        android:layout_
        android:layout_
        android:orientation="vertical" >

            <ScrollView
                android:id="@+id/formScrollView"
                android:layout_
                android:layout_ >

                <LinearLayout
                    android:layout_
                    android:layout_
                    android:layout_weight="1"
                    android:orientation="vertical" >

                    <!-- The "Form", with EditTexts, Checkboxes and whatnot... -->

                </LinearLayout>
            </ScrollView>

    </LinearLayout>
</RelativeLayout>

通过这个布局,我完成了两件事:

formScrollView,其中包含带有 EditTexts 等的“表单”。当软键盘出现时,它会重新调整大小(如我所愿)并可以滚动。

包含 ImageView 的backgroundScrollView 不会重新调整大小(因为android:isScrollContainer="false")并填充整个视口(@98​​7654326@)。因此,无论键盘是否可见,背景都保持不变。

不是一个完美的解决方案,因为 ImageView 的大小变化很小,但目前结果已经足够好了。如果有人知道更好的解决方案,请告诉我。

【讨论】:

这太糟糕了,您使用 2 个视图组,甚至是重布局权重,只是为了缩放图像!【参考方案4】:

答案中的解决方案是最好的,使用它:

Resources res = getActivity().getResources();
Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.some_image);
BackgroundBitmapDrawable background = new BackgroundBitmapDrawable(res, bitmap);
view.setBackgroundDrawable(background);

view.setBackground(background);

适用于 API 16 及更高版本。

【讨论】:

【参考方案5】:

在您的 xml 中创建一个 ImageView。 将其放在您的布局标签下方。 将 translationZ 设置为正值,例如 10dp,将 scaleType 设置为 centerCrop。

【讨论】:

不确定这对其他答案有何改进。无论如何,为什么不添加代码来说明您的意思?【参考方案6】:

更改您的 ScaleType

 android:scaleType="FitXY"

在你的清单中

<activity
           android:name=".activity.MainActivity"
            android:windowSoftInputMode="adjustPan|stateHidden" />

【讨论】:

以上是关于如何拥有不调整大小的全屏背景图像(带中心裁剪)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 GraphicsMagick 从图像中裁剪/调整背景/边框的大小

使用 php Imagick 保留背景调整 PNG 的大小

PHP图像调整大小和裁剪功能

裁剪背景图像,然后调整大小以适应特定尺寸 CSS/HTML

PHP:图像调整大小和裁剪为纵向

裁剪和调整图像大小 - 是不是可以使用 <%= image_tag %> 设置背景图像?