web-dev-qa-db-fra.com

Comment avoir une image de fond plein écran (avec un recadrage au centre) qui ne redimensionne pas

J'essaie de définir une photo comme arrière-plan d'une Activity. Je veux que l'arrière-plan soit une image en plein écran (sans bordure) }.

Comme je veux que l’image remplisse tout le fond de l’activité sans s’étirer ni se pincer (c’est-à-dire des redimensionnements non proportionnels pour X et Y) et que cela ne me dérange pas que la photo soit rognée, j’utilise une RelativeLayout avec une ImageView (avec Android:scaleType="centerCrop" ) et le reste de ma mise en page consistant en un ScrollView et ses enfants.

<!-- 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>

Le problème est que le reste de la mise en page comporte des vues EditText et lorsque le clavier logiciel apparaît, ImageView est redimensionné. Je voudrais que le fond reste le même, peu importe si le clavier est visible ou non.

J'ai vu beaucoup de questions sur SO à propos de la redimensionnement d'ImageViews mais (imo) pas de réponses satisfaisantes. La plupart d'entre eux consistent simplement à définir le Android:windowSoftInputMode="adjustPan" - ce qui n'est pas toujours pratique, surtout si vous souhaitez que l'utilisateur puisse faire défiler l'activité - ou à l'aide de getWindow().setBackgroundDrawable() qui ne recadre pas l'image.

J'ai réussi à sous-classer ImageView et à remplacer son onMeasure() (voir ma réponse ici: ImageView redimensionne lorsque le clavier est ouvert ) afin qu'il puisse forcer une hauteur et une largeur fixes - égales aux dimensions de l'écran du périphérique - selon à un drapeau, mais je me demande s’il existe un meilleur moyen d’obtenir le résultat souhaité.

Donc, pour résumer, ma question est la suivante: Comment puis-je définir le fond d’une activité pour qu’il s’agisse d’une photo en plein écran  

  • avec scale type = "centerCrop", de sorte que la photo soit mise à l’échelle de manière uniforme (en conservant son rapport de format) et que les deux dimensions (largeur et hauteur) seront égales ou supérieures à la dimension correspondante de la vue;

  • cela ne sera pas redimensionné quand un clavier logiciel apparaît;


REPONSE:

J'ai fini par suivre les conseils de @ pskink et sous-classer BitmapDrawable (voir sa réponse ci-dessous). J'ai dû faire quelques ajustements pour m'assurer que la BackgroundBitmapDrawable est toujours réduite et recadrée de manière à remplir l'écran.

Voici mon dernier cours, adapté de sa réponse:

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

Ensuite, il suffit de créer une BackgroundBitmapDrawable et de la définir comme arrière-plan de la vue racine.

17
user1987392

essayez ce 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>

et réglez-le à votre disposition de niveau supérieur: Android: background = "@ drawable/backlayer"

UPDATE: essayez ce BitmapDrawadle, réglez-le sur la disposition de niveau supérieur (setBackgroundDrawable ()), si simpleMapping == true est suffisant, vous pouvez supprimer la branche "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);
    }
}
7
pskink

Après m'être gratté la tête pendant un moment, cela a été ma solution "bonne pour le moment".

Partage d'un exemple de XML pouvant être utile à quiconque.

<!-- 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>

Avec cette mise en page, je réalise deux choses:

  • formScrollView, qui contient le "formulaire" avec les EditTexts, etc., est redimensionné (comme je le souhaite) et défile lorsque le clavier logiciel est affiché.

  • backgroundScrollView, qui contient ImageView, n'est pas redimensionné (à cause du Android:isScrollContainer="false" ) et remplit la fenêtre d'affichage entière (Android:fillViewport="true"). Ainsi, l’arrière-plan reste le même, que le clavier soit visible ou non. 

Ce n’est pas une solution parfaite, car la variable ImageView souffre d’une très petite variation de sa taille, mais le résultat est suffisant pour le moment. Si quelqu'un connaît une meilleure solution, s'il vous plaît faites le moi savoir.

0
user1987392

Mettez tout dans un FrameLayout, avec au premier enfant une imageview qui va correspondre à son parent (le frameLayout) et vous le configurez comme vous le souhaitez, et devant la vue, vous mettez ce que vous voulez (LinearLayout, je suppose)

0
Adnane.T

Changer votre ScaleType

 Android:scaleType="FitXY"

et dans votre manifeste 

<activity
           Android:name=".activity.MainActivity"
            Android:windowSoftInputMode="adjustPan|stateHidden" />
0
Kushal Ramola

La solution dans la réponse est la meilleure, pour l'utiliser:

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

ou

view.setBackground(background);

pour API 16 et plus.

0
Meanman