Android: Skalować obraz do rysowania lub tła?

137

W układzie chcę przeskalować obraz tła (zachowując jego proporcje) do miejsca przydzielonego podczas tworzenia strony. Czy ktoś ma pomysł jak to zrobić?

Używam layout.setBackgroundDrawable()i a BitmapDrawable()do ustawiania gravityprzycinania i wypełniania, ale nie widzę żadnej opcji skalowania.

finnmglas
źródło
Miałem ten problem i zastosowałem się do zalecenia Dweebo ze zmianą sugerowaną przez Anke: zmieniłem fitXY na fitCenter. Działało dobrze.

Odpowiedzi:

91

Aby dostosować skalowanie obrazu tła, utwórz taki zasób:

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:gravity="center"
    android:src="@drawable/list_bkgnd" />

Następnie zostanie wyśrodkowany w widoku, jeśli zostanie użyty jako tło. Istnieją również inne flagi: http://developer.android.com/guide/topics/resources/drawable-resource.html

Aleks N.
źródło
6
Próbowałem użyć bitmapy, ale obraz jest po prostu wyśrodkowany, a nie skalowany, jak prosi Sam. Kiedy używam ImageView, działa pięknie, bez kłopotów. (uwaga: ja też nie przewijam w moim przypadku). Do czego dodałbym bitmapę, aby przeskalować obraz?
Joe D'Andrea
7
Możliwe wartości to: „top” | „dół” | „w lewo” | „dobrze” | "center_vertical" | "fill_vertical" | "środek_poziome" | "fill_horizontal" | „środek” | "wypełnij" | "clip_vertical" | "clip_horizontal"
Aleks N.
32
żaden z tych parametrów nie skaluje bitmapy, zachowując jej współczynnik proporcji, więc odpowiedź jest błędna.
Malachiasz
7
Drogi Malachiaszu, „środek” nie narusza proporcji. Ale nie zmniejszy to obrazu. Co oznacza, że ​​jest to nieco niebezpieczna funkcja. W zależności od rozdzielczości celu nigdy nie wiadomo, jak skalowana rzecz wyjdzie dokładnie. Kolejne marne rozwiązanie Google.
2
masz sposób na dodanie atrybutu scaleType?
ademar111190
57

Nie próbowałem robić dokładnie tego, co chcesz, ale możesz skalować ImageView za pomocą android:scaleType="fitXY"
i będzie on dopasowany do dowolnego rozmiaru, który nadasz ImageView.

Możesz więc utworzyć FrameLayout dla swojego układu, umieścić w nim ImageView, a następnie dowolną inną zawartość, której potrzebujesz w FrameLayout, z przezroczystym tłem.

<FrameLayout  
android:layout_width="fill_parent" android:layout_height="fill_parent">

  <ImageView 
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:src="@drawable/back" android:scaleType="fitXY" />

  <LinearLayout>your views</LinearLayout>
</FrameLayout>
dweebo
źródło
1
Czy można to zrobić na Surface? Jeśli nie, czy mogę wyświetlić podgląd (dla aplikacji do nagrywania) za pomocą FrameLayout?
Namratha
1
@dweebo: Czy próbowałeś tego? Wydaje się, że to nie działa dla mnie.
speedplane
3
Ocena obniżona, ponieważ używanie dwóch komponentów interfejsu użytkownika zamiast jednego jest złym pomysłem, szczególnie w przypadku tak obciążonych komponentów, jak ListView. Najprawdopodobniej będziesz mieć problemy podczas przewijania. Właściwe rozwiązanie: stackoverflow.com/a/9362168/145046
Aleks N.
1
Wypróbowałem scaleType fitCenter i centerCrop (wraz z kilkoma odmianami rysowania / obrazu dla różnych gęstości) i zadziałało! Każdy rodzaj skali miał dla mnie sens - podejrzewam, że bardziej osobiste preferencje oparte na obrazie. Zauważ, że użyłem scalania zamiast FrameLayout i w moim konkretnym przypadku nie ma przewijania.
Joe D'Andrea
2
centerInside jest właściwa, nie fitXY, jak fitXY nie zachowuje proporcje obrazu
Malachiasz
35

Można to zrobić w łatwy sposób z poziomu rysunku:

your_drawable.xml

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

    <item android:drawable="@color/bg_color"/>
    <item>
        <bitmap
            android:gravity="center|bottom|clip_vertical"
            android:src="@drawable/your_image" />
    </item>

</layer-list>

Jedynym minusem jest to, że jeśli nie ma wystarczającej ilości miejsca, obraz nie zostanie w pełni pokazany, ale zostanie przycięty, nie mogłem znaleźć sposobu, aby to zrobić bezpośrednio z rysunku. Ale z testów, które zrobiłem, działa całkiem dobrze i nie przycina zbyt dużej części obrazu. Możesz grać więcej z opcjami grawitacji.

Innym sposobem będzie po prostu stworzyć układ, gdzie można użyć ImageViewi ustawić scaleTypesię fitCenter.

Ionut Negru
źródło
2
To rozwiązanie jest zdecydowanie tym, czego szukałem przez długi czas, aby zastąpić właściwość tła mojego motywu i uniknąć innej skali obrazu, gdy widok ma karty lub nie. Dzięki za udostępnienie tego.
Bibu
1
Pokazuje błąd:error: <item> must have a 'name' attribute.
Dmitry
18

Aby zachować proporcje trzeba użyć android:scaleType=fitCenterlub fitStartitd. Używanie fitXYnie będzie zachować oryginalne proporcje obrazu!

Zauważ, że działa to tylko dla obrazów z srcatrybutem, a nie dla obrazu tła.

Anke
źródło
18

Użyj obrazu jako tła dopasowanego do układu:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <ImageView
        android:id="@+id/imgPlaylistItemBg"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:adjustViewBounds="true"
        android:maxHeight="0dp"
        android:scaleType="fitXY"
        android:src="@drawable/img_dsh" />

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


    </LinearLayout>

</FrameLayout>
Konstantin Konopko
źródło
Dzięki! android:scaleType="centerCrop"Działa również .
CoolMind
5

Po ustawieniu Drawable z ImageView przy użyciu setBackgroundDrawablemetody obraz będzie zawsze skalowany. Parametry jako adjustViewBoundslub różne ScaleTypesbędą po prostu ignorowane. Jedynym rozwiązaniem, aby zachować proporcje, które znalazłem, jest zmiana rozmiaru ImageViewpo załadowaniu rysunku. Oto fragment kodu, którego użyłem:

// bmp is your Bitmap object
int imgHeight = bmp.getHeight();
int imgWidth = bmp.getWidth();
int containerHeight = imageView.getHeight();
int containerWidth = imageView.getWidth();
boolean ch2cw = containerHeight > containerWidth;
float h2w = (float) imgHeight / (float) imgWidth;
float newContainerHeight, newContainerWidth;

if (h2w > 1) {
    // height is greater than width
    if (ch2cw) {
        newContainerWidth = (float) containerWidth;
        newContainerHeight = newContainerWidth * h2w;
    } else {
        newContainerHeight = (float) containerHeight;
        newContainerWidth = newContainerHeight / h2w;
    }
} else {
    // width is greater than height
    if (ch2cw) {
        newContainerWidth = (float) containerWidth;
        newContainerHeight = newContainerWidth / h2w; 
    } else {
        newContainerWidth = (float) containerHeight;
        newContainerHeight = newContainerWidth * h2w;       
    }
}
Bitmap copy = Bitmap.createScaledBitmap(bmp, (int) newContainerWidth, (int) newContainerHeight, false);
imageView.setBackgroundDrawable(new BitmapDrawable(copy));
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
imageView.setLayoutParams(params);
imageView.setMaxHeight((int) newContainerHeight);
imageView.setMaxWidth((int) newContainerWidth);

W powyższym fragmencie kodu bmp to obiekt Bitmap, który ma być wyświetlany, a imageView to ImageViewobiekt

Ważną rzeczą, na którą należy zwrócić uwagę, jest zmiana parametrów układu . Jest to konieczne, ponieważ setMaxHeighti setMaxWidthbędzie miało znaczenie tylko wtedy, gdy szerokość i wysokość są zdefiniowane tak, aby zawijać zawartość, a nie wypełniać element nadrzędny. Z drugiej strony Fill parent jest pożądanym ustawieniem na początku, ponieważ w przeciwnym razie containerWidthi containerHeightoba będą miały wartości równe 0. Tak więc w pliku układu będziesz miał coś takiego dla swojego ImageView:

...
<ImageView android:id="@+id/my_image_view"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"/>
...
maddob
źródło
4

Nie jest to najbardziej wydajne rozwiązanie, ale jak ktoś zasugerował zamiast tła, możesz utworzyć FrameLayout lub RelativeLayout i użyć ImageView jako pseudo tła - inne elementy będą umieszczone tuż nad nim:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <ImageView
        android:id="@+id/ivBackground"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:scaleType="fitStart"
        android:src="@drawable/menu_icon_exit" />

    <Button
        android:id="@+id/bSomeButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="61dp"
        android:layout_marginTop="122dp"
        android:text="Button" />

</RelativeLayout>

Problem z ImageView polega na tym, że dostępne są tylko typy skali: CENTER, CENTER_CROP, CENTER_INSIDE, FIT_CENTER, FIT_END, FIT_START, FIT_XY, MATRIX ( http://etcodehome.blogspot.de/2011/05/android-imageview-scaletype-samples.html )

i aby „przeskalować obraz tła (zachowując proporcje)” w niektórych przypadkach, gdy chcesz, aby obraz wypełnił cały ekran (na przykład obraz tła), a współczynnik proporcji ekranu jest inny niż obraz, wymagany typ skali jest miły z TOP_CROP, ponieważ:

CENTER_CROP centruje przeskalowany obraz zamiast wyrównywać górną krawędź do górnej krawędzi widoku obrazu, a FIT_START dopasowuje się do wysokości ekranu i nie wypełnia szerokości. I jak zauważył użytkownik Anke, FIT_XY nie zachowuje proporcji.

Na szczęście ktoś rozszerzył ImageView o obsługę TOP_CROP

public class ImageViewScaleTypeTopCrop extends ImageView {
    public ImageViewScaleTypeTopCrop(Context context) {
        super(context);
        setup();
    }

    public ImageViewScaleTypeTopCrop(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup();
    }

    public ImageViewScaleTypeTopCrop(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup();
    }

    private void setup() {
        setScaleType(ScaleType.MATRIX);
    }

    @Override
    protected boolean setFrame(int frameLeft, int frameTop, int frameRight, int frameBottom) {

        float frameWidth = frameRight - frameLeft;
        float frameHeight = frameBottom - frameTop;

        if (getDrawable() != null) {

            Matrix matrix = getImageMatrix();
            float scaleFactor, scaleFactorWidth, scaleFactorHeight;

            scaleFactorWidth = (float) frameWidth / (float) getDrawable().getIntrinsicWidth();
            scaleFactorHeight = (float) frameHeight / (float) getDrawable().getIntrinsicHeight();

            if (scaleFactorHeight > scaleFactorWidth) {
                scaleFactor = scaleFactorHeight;
            } else {
                scaleFactor = scaleFactorWidth;
            }

            matrix.setScale(scaleFactor, scaleFactor, 0, 0);
            setImageMatrix(matrix);
        }

        return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
    }

}

https://stackoverflow.com/a/14815588/2075875

Teraz IMHO byłoby idealne, gdyby ktoś napisał niestandardowy Drawable, który skaluje obraz w ten sposób. Wtedy może być użyty jako parametr tła.


Reflog sugeruje przeskalowanie rysunku przed jego użyciem. Oto instrukcja, jak to zrobić: Java (Android): jak skalować element do rysowania bez mapy bitowej? Chociaż ma tę wadę, że skalowanie do rysowania / mapy bitowej będzie zużywać więcej pamięci RAM, podczas gdy skalowanie w locie używane przez ImageView nie wymaga więcej pamięci. Zaletą może być mniejsze obciążenie procesora.

Malachiasz
źródło
Niezłe! Robi dokładnie to, o co mi chodziło: obraz tła, aby wypełnić cały ekran, zwiększając proporcjonalnie szerokość i przycinając dolną część obrazu.
CMash
4

Poniższy kod sprawia, że ​​bitmapa jest idealna z tym samym rozmiarem obrazu. Uzyskaj wysokość i szerokość obrazu bitmapowego, a następnie oblicz nową wysokość i szerokość za pomocą parametrów imageview. To daje wymagany obraz o najlepszym współczynniku proporcji.

int bwidth=bitMap1.getWidth();
int bheight=bitMap1.getHeight();
int swidth=imageView_location.getWidth();
int sheight=imageView_location.getHeight();
new_width=swidth;
new_height = (int) Math.floor((double) bheight *( (double) new_width / (double) bwidth));
Bitmap newbitMap = Bitmap.createScaledBitmap(bitMap1,new_width,new_height, true);
imageView_location.setImageBitmap(newbitMap)
Naresh Sharma
źródło
2

To, co zaproponował Dweebo, działa. Ale moim skromnym zdaniem jest to niepotrzebne. Tło do rysowania dobrze się skaluje. Widok powinien mieć stałą szerokość i wysokość, jak w poniższym przykładzie:

 < RelativeLayout 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/black">

    <LinearLayout
        android:layout_width="500dip"
        android:layout_height="450dip"
        android:layout_centerInParent="true"
        android:background="@drawable/my_drawable"
        android:orientation="vertical"
        android:padding="30dip"
        >
        ...
     </LinearLayout>
 < / RelativeLayout>
Yar
źródło
2
Rozumiem, że tło rozszerzy się, aby wypełnić widok, ale nie zmniejszy się.
Edward Falk
1

Jedną z opcji do wypróbowania jest umieszczenie obrazu w drawable-nodpifolderze i ustawienie tła układu na identyfikator zasobu do rysowania.

To zdecydowanie działa ze skalowaniem w dół, chociaż nie testowałem skalowania w górę.

Sababado
źródło
0

Kotlin:

Jeśli potrzebujesz narysować bitmapę w Widoku, przeskalowany do FIT.

Możesz wykonać odpowiednie obliczenia, aby ustawić bm wysokość równą kontenerowi i dostosować szerokość, w przypadku gdy stosunek szerokości bm do wysokości jest mniejszy niż stosunek szerokości do wysokości kontenera lub odwrotnie w przeciwnym scenariuszu.

Zdjęcia:

Przykładowy obraz 1

Przykładowy obraz 2

// binding.fragPhotoEditDrawCont is the RelativeLayout where is your view
// bm is the Bitmap
val ch = binding.fragPhotoEditDrawCont.height
val cw = binding.fragPhotoEditDrawCont.width
val bh = bm.height
val bw = bm.width
val rc = cw.toFloat() / ch.toFloat()
val rb = bw.toFloat() / bh.toFloat()

if (rb < rc) {
    // Bitmap Width to Height ratio is less than Container ratio
    // Means, bitmap should pin top and bottom, and have some space on sides.
    //              _____          ___
    // container = |_____|   bm = |___| 
    val bmHeight = ch - 4 //4 for container border
    val bmWidth = rb * bmHeight //new width is bm_ratio * bm_height
    binding.fragPhotoEditDraw.layoutParams = RelativeLayout.LayoutParams(bmWidth.toInt(), bmHeight)
}
else {
    val bmWidth = cw - 4 //4 for container border
    val bmHeight = 1f/rb * cw
    binding.fragPhotoEditDraw.layoutParams = RelativeLayout.LayoutParams(bmWidth, bmHeight.toInt())
}
danitinez
źródło
-4

będziesz musiał wstępnie skalować ten rysunek, zanim użyjesz go jako tła

reflog
źródło
Czy to naprawdę jedyna opcja? Czy nie ma typu kontenera, w którym można umieścić ImageView, w którym można określić rozmiar wyświetlania?
GrkEngineer
Mam również problem z SurfaceView
AZ_
3
Nie ma nic takiego jak dopasowanie proporcji, wypełnienie aspektu, lewy górny, górny itp., Jak na iOS? To jest do bani ...
Maciej Swic
Jak powiedział @Aleksej, nieprawdziwa odpowiedź.
uczeń
-5

Możesz użyć jednego z następujących:

android: gravity = "fill_horizontal | clip_vertical"

Lub

android: gravity = "fill_vertical | clip_horizontal"

Eli Baynesay
źródło