19

I'm trying to set a photo as the background of an Activity. I want the background to be a full screen image (no borders).

As I want the image to fill the entire activity's background without stretching/squeezing (i.e. unproportional scalings for X and Y) and I don't mind if the photo has to be cropped, I'm using a RelativeLayout with an ImageView (with android:scaleType="centerCrop") and the rest of my layout consisting of a ScrollView and its children.

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

The problem is that the rest of the layout has some EditText views and when the softkeyboard shows up, the ImageView gets re-sized. I would like the background to remain the same, irregardless of whether the softkeyboard is visible or not.

I have seen plenty of questions on SO about ImageViews being re-sized but (imo) no satisfactory answers. Most of them just consist of setting the android:windowSoftInputMode="adjustPan" - which is not always practical, especially if you want the user to be able to scroll in the activity - or using getWindow().setBackgroundDrawable() which doesn't crop the image.

I've managed to sub-class ImageView and override its onMeasure() (see my answer here: ImageView resizes when keyboard open) so that it can force a fixed height & width - equal to the device's screen dimensions - according to a flag but I'm wondering if there's a better way of achieving the result I want.

So, to sum up, my question is: How can I set an Activity's background to be a full-screen photo

  • with scale type = "centerCrop", so that the photo is scaled uniformly (maintaining its aspect ratio) and therefore both dimensions (width and height) of will be equal to or larger than the corresponding dimension of the view;

  • that doesn't get resized when a softkeyboard pops up;


ANSWER:

I ended up following @pskink's advice and subclassed BitmapDrawable (see his answer bellow). I had to do some adjustments to make sure that the BackgroundBitmapDrawable is always scaled and cropped in a way that fills the screen.

Here's my final class, adapted from his answer:

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);
    }
}

Then its just a matter of creating a BackgroundBitmapDrawable and setting it as the root View's background.

Community
  • 1
  • 1
user1987392
  • 3,921
  • 4
  • 34
  • 59
  • Can you post your XML? This sounds like you want the background of the activity to not scale (which could be an imageView in a relativelayout with a scrollview containing your other elements) but you are overriding imageview and then mention an EditText. Your XML may help clarify the issue. – Jim Feb 16 '14 at 15:54
  • @Jim I've posted some XML. Overriding ImageView is just a possible solution I've thought about, but I'd rather avoid it if I can. – user1987392 Feb 16 '14 at 16:00
  • dont use ImageView, use proper BitmapDrawable (with top or center gravity) as your layout background – pskink Feb 16 '14 at 16:02
  • I think FrameLayout is your problem. I'm pretty sure I've done what you want using RelativeLayout without a problem. Docs for FrameLayout recommend only 1 child or you might have problems (http://developer.android.com/reference/android/widget/FrameLayout.html) – Jim Feb 16 '14 at 16:04
  • Changed to RelativeLayout, got the same result: when the softkeyboard shows up the whole View (including the ImageView) is resized causing the background to be be cropped in a different fashion). – user1987392 Feb 16 '14 at 16:09
  • @user1987392 dont use ImageView see my comment above – pskink Feb 16 '14 at 16:11
  • @pskink could you please elaborate? Do you mean creating a BitmapDrawable and setting it as the root layout's background in my onCreate()? I've tried that but when I do it the background doesn't occupy the whole screen (i.e. white borders). – user1987392 Feb 16 '14 at 16:19
  • @user1987392 so use LayerDrawable with black bottom layer – pskink Feb 16 '14 at 16:25
  • 1
    I'm assuming you've tried all the stuff here (I think "isScrollContainter" solved my problem, but I can't find it now): http://stackoverflow.com/questions/4207880/android-how-do-i-prevent-the-soft-keyboard-from-pushing-my-view-up – Jim Feb 16 '14 at 16:41
  • @pskink it has to be a fullscreen background, like MiguelC from this question wants: http://stackoverflow.com/questions/16135984/full-screen-background-image-in-an-activity – user1987392 Feb 16 '14 at 17:15
  • @user1987392 ok it can be full screen, whats the peoblem? – pskink Feb 16 '14 at 17:51
  • 1
    Amazing. This is the best solution, going to post some code below for usage. – Meanman Apr 23 '14 at 15:47
  • Excellent, no need for ImageView! you can even extract the bitmap from the existing background attribute. – Nicolas Cornette Apr 28 '16 at 09:16

6 Answers6

7

try this 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>

and set it to your top level layout: android:background="@drawable/backlayer"

UPDATE: try this BitmapDrawadle, set it to top level layout (setBackgroundDrawable()), if simpleMapping == true is good enough you can remove "else" branch:

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);
    }
}
Houcine
  • 24,001
  • 13
  • 56
  • 83
pskink
  • 23,874
  • 6
  • 66
  • 77
  • That doesn't guarantee that the whole viewport will be filled with the image like in http://stackoverflow.com/questions/16135984/full-screen-background-image-in-an-activity. Wouldn't this solution allow a scenario where the image fills (for example) only half of the screen and the rest of the screen is filled by a solid color, depending on the orientation, screen density, size, etc.? That's not what I'm trying to achieve. See the link in this comment. – user1987392 Feb 16 '14 at 21:32
  • @user1987392 you say no "stretching/squeezing" so it meams no scaling at all and now you say if screen is bigger than image then do image scaling? – pskink Feb 16 '14 at 21:43
  • Ok maybe my words weren't chosen wisely: by stretching/squeezing I meant the image not being re-sized with the same proportion for its height and width. That would obviously make the photo look weird. I do want scaling but I want it to be "center-crop". – user1987392 Feb 16 '14 at 21:48
1

The solution in the answer is the best, to use it:

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

or

view.setBackground(background);

for API 16 and above.

Meanman
  • 1,474
  • 20
  • 17
0

Put everything in a FrameLayout, with first child an imageview which is going to match it's parent (the frameLayout) and you configure it as you want your background, and in front of the image view you put whatever you want (LinearLayout I suppose)

Adnane.T
  • 280
  • 1
  • 4
  • 13
  • That's exactly my problem. And I've tried both with a FrameLayout and RelativeLayout. The problem is that when the softkeyboard shows up, the ImageView in the background gets re-sized. Even though the ImageView maintains its proportions, I'd prefer if it maintained the same appearance whether the keyboard is hidden or shown. – user1987392 Feb 16 '14 at 17:02
  • even configuring it to crop? – Adnane.T Feb 17 '14 at 10:10
  • Yes, that's the first thing I tried: Frame/RelativeLayout with the ImageView (centerCrop). The image would get re-sized when the keyboard was displayed. See the accepted answer + my edit :) – user1987392 Feb 17 '14 at 11:18
0

After scratching my head for a while this has been my "good for now" solution.

Sharing a sample XML that might be useful for anyone.

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

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

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <!-- The FullScreen, Center-Cropped Image Background --> 
            <ImageView
                android:id="@+id/backgroundImage"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                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_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

            <ScrollView
                android:id="@+id/formScrollView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_weight="1"
                    android:orientation="vertical" >

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

                </LinearLayout>
            </ScrollView>

    </LinearLayout>
</RelativeLayout>

With this layout, I accomplish two things:

  • formScrollView, which contains the "form" with the EditTexts, etc. gets re-sized (as I want it to) and scrollable when the softkeyboard shows up.

  • backgroundScrollView, which contains the ImageView, doesn't get re-sized (because of the android:isScrollContainer="false") and fills the entire viewport (android:fillViewport="true"). Thus the background remains the same irregardless of whether the keyboard is visible or not.

Not a perfect solution as the ImageView somehow suffers a very small variation in its size, but the result is good enough for now. If someone knows a better solution, please let me know.

Community
  • 1
  • 1
user1987392
  • 3,921
  • 4
  • 34
  • 59
  • this is terrible, you are using 2 viewgroups, even heavy layout weights, just to scale an image! – rupps Aug 19 '14 at 10:51
-1

Change your ScaleType

 android:scaleType="FitXY"

and in your manifest

<activity
           android:name=".activity.MainActivity"
            android:windowSoftInputMode="adjustPan|stateHidden" />
Kushal Ramola
  • 151
  • 1
  • 5
-2

Create an ImageView in your xml. Place it below your Layout tag. Set the translationZ to a positive value say 10dp and the scaleType to centerCrop.

Kjk
  • 1
  • Not sure how this improves on the other answers. Anyway, why not add code to illustrate what you mean? – Huey Apr 30 '15 at 12:58