Konwertowanie widoku na mapę bitową bez wyświetlania go w systemie Android?


137

Postaram się wyjaśnić, co dokładnie muszę zrobić.

Mam 3 oddzielne ekrany, na przykład A, B, C. Jest jeszcze jeden ekran o nazwie HomeScreen, na którym wszystkie 3 ekrany powinny być wyświetlane w widoku galerii, a użytkownik może wybrać, w którym widoku chce przejść.

Udało mi się uzyskać mapy bitowe wszystkich 3 ekranów i wyświetlić je w widoku galerii, umieszczając cały kod tylko w Aktywności ekranu głównego. To bardzo skomplikowało kod i chciałbym go uprościć.

Czy mogę więc wywołać inną aktywność z HomeScreen i nie wyświetlać jej, a po prostu pobrać mapę bitową tego ekranu. Na przykład, powiedzmy, że po prostu wywołuję HomeScreen i wywołuje działanie A, B, C i żadne działania z A, B, C nie są wyświetlane. Po prostu podaje bitmapę tego ekranu przez getDrawingCache (). Następnie możemy wyświetlić te mapy bitowe w widoku galerii w HomeScreen.

Mam nadzieję, że bardzo jasno wyjaśniłem problem.

Daj mi znać, jeśli jest to rzeczywiście możliwe.


1
Nie jestem do końca pewien, ale myślę, że nie będziesz w stanie tego zrobić. Problem polega na tym, że działania mają być wyświetlane użytkownikowi. Możesz rozpocząć działanie, a następnie natychmiast je ukryć, ale będzie ono nadal widoczne dla użytkownika przez ułamek sekundy. Jest wyświetlany wystarczająco długo, aby można go było zauważyć, więc kilkakrotne migotanie ekranu sprawia, że ​​aplikacja wygląda nieprofesjonalnie. Jednak może się zdarzyć, że istnieje polecenie uruchomienia działania bez jego wyświetlania; Po prostu nie wiem, czy taki istnieje.
Steve Haley

5
Właściwie udało mi się to zrobić.
sunil

Och, jak możesz nazwać to działanie, ale nie pokazywać go? Czy mogę przyjąć układ bieżącego działania jako szablon do generowania bitmapy, jednocześnie dostarczając do niego różne treści?
zionpi,

Sprawdź odpowiedź w tym poście, znalazłem jakieś rozwiązanie: stackoverflow.com/questions/36424381/ ...
Wackaloon

Odpowiedzi:


215

jest na to sposób. musisz stworzyć Bitmapę i Canvas i wywołać view.draw (canvas);

oto kod:

public static Bitmap loadBitmapFromView(View v) {
    Bitmap b = Bitmap.createBitmap( v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_8888);                
    Canvas c = new Canvas(b);
    v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
    v.draw(c);
    return b;
}

jeśli widok nie został wyświetlony przed jego rozmiarem, będzie wynosił zero. Można to zmierzyć w ten sposób:

if (v.getMeasuredHeight() <= 0) {
    v.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    Bitmap b = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(b);
    v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
    v.draw(c);
    return b;
}

EDIT: według tego postu , Przechodząc WRAP_CONTENTjako wartość na makeMeasureSpec()nie coś dobrego (choć dla niektórych klas widzenia to działa), a metoda zalecana jest:

// Either this
int specWidth = MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.AT_MOST);
// Or this
int specWidth = MeasureSpec.makeMeasureSpec(0 /* any */, MeasureSpec.UNSPECIFIED);
view.measure(specWidth, specWidth);
int questionWidth = view.getMeasuredWidth();

1
Próbowałem tego, ale wszystko, co otrzymałem, to półprzezroczyste czarne pudełko. Czy muszę coś zrobić z widokiem, aby przygotować go do rysowania bitmapowego?
Bobbake4

4
Właściwie musiałem to zmienić, aby v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());to działało poprawnie, ale dzięki za kod :)
triada

4
Musiałem użyć v.getWidth () zamiast v.getLayoutParams (). Width i podobnych dla wysokości. W przeciwnym razie teraz działa.
David Manpearl

1
Użyłem v.measure(0, 0); v.getMeasuredWidth(); v.getMeasuredHeight();.
Brais Gabin

7
Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);Działa lepiej
Pierre,

31

oto moje rozwiązanie:

public static Bitmap getBitmapFromView(View view) {
    Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(returnedBitmap);
    Drawable bgDrawable =view.getBackground();
    if (bgDrawable!=null) 
        bgDrawable.draw(canvas);
    else 
        canvas.drawColor(Color.WHITE);
    view.draw(canvas);
    return returnedBitmap;
}

Cieszyć się :)


Dzięki. Miałem problemy na niektórych urządzeniach, jeśli wysokość przekraczała określoną wartość. Nie przetestowałem w pełni, ale wydaje się, że to rozwiązuje.
BMF

23

Spróbuj tego,

/**
 * Draw the view into a bitmap.
 */
public static Bitmap getViewBitmap(View v) {
    v.clearFocus();
    v.setPressed(false);

    boolean willNotCache = v.willNotCacheDrawing();
    v.setWillNotCacheDrawing(false);

    // Reset the drawing cache background color to fully transparent
    // for the duration of this operation
    int color = v.getDrawingCacheBackgroundColor();
    v.setDrawingCacheBackgroundColor(0);

    if (color != 0) {
        v.destroyDrawingCache();
    }
    v.buildDrawingCache();
    Bitmap cacheBitmap = v.getDrawingCache();
    if (cacheBitmap == null) {
        Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
        return null;
    }

    Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);

    // Restore the view
    v.destroyDrawingCache();
    v.setWillNotCacheDrawing(willNotCache);
    v.setDrawingCacheBackgroundColor(color);

    return bitmap;
}

Jak go używać z mojej głównej klasy aktywności?
Si8,

To jest przestarzałe
Ch Vas

22

Wiem, że może to być nieaktualny problem, ale miałem problemy z uruchomieniem któregokolwiek z tych rozwiązań. W szczególności stwierdziłem, że jeśli w widoku zostaną wprowadzone jakiekolwiek zmiany po jego zawyżeniu, zmiany te nie zostaną uwzględnione w renderowanej mapie bitowej.

Oto metoda, która sprawdziła się w moim przypadku. Z jednym zastrzeżeniem. przed zadzwonieniem getViewBitmap(View)zawyżyłem swój widok i poprosiłem o układ ze znanymi wymiarami. Było to potrzebne, ponieważ mój układ widoku powodowałby zerową wysokość / szerokość do momentu umieszczenia treści w środku.

View view = LayoutInflater.from(context).inflate(layoutID, null);
//Do some stuff to the view, like add an ImageView, etc.
view.layout(0, 0, width, height);

Bitmap getViewBitmap(View view)
{
    //Get the dimensions of the view so we can re-layout the view at its current size
    //and create a bitmap of the same size 
    int width = view.getWidth();
    int height = view.getHeight();

    int measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
    int measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);

    //Cause the view to re-layout
    view.measure(measuredWidth, measuredHeight);
    view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());

    //Create a bitmap backed Canvas to draw the view into
    Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(b);

    //Now that the view is laid out and we have a canvas, ask the view to draw itself into the canvas
    view.draw(c);

    return b;
}

„Magiczny sos” dla mnie został znaleziony tutaj: https://groups.google.com/forum/#!topic/android-developers/BxIBAOeTA1Q

Twoje zdrowie,

Levi


Twoje zdrowie! Wygląda na to, że po wszelkich zmianach w układzie trzeba wywołać Measure i
requestLayout

1
Dzięki za to rozwiązanie! Miałem ten sam problem. Używałem measure()i layout()zanim wypełniłem widok, więc miałem dziwne wyniki. Przenosząc te wywołania w dół, powyżej createBitmap()naprawiłem to dla mnie!
Sven Jacobs

9

W systemie Android KTX jest świetna funkcja rozszerzenia Kotlin: View.drawToBitmap(Bitmap.Config)


7
To nie zadziała, jeśli widok nie jest przedstawiony w układzie. Błąd: „IllegalStateException: należy określić widok przed wywołaniem funkcji drawToBitmap ()”
Val

3

Układ lub widok do mapy bitowej:

 private Bitmap createBitmapFromLayout(View tv) {      
    int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    tv.measure(spec, spec);
    tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight());
    Bitmap b = Bitmap.createBitmap(tv.getMeasuredWidth(), tv.getMeasuredWidth(),
            Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(b);
    c.translate((-tv.getScrollX()), (-tv.getScrollY()));
    tv.draw(c);
    return b;
}

Metoda wywołania:

Bitmap src = createBitmapFromLayout(View.inflate(this, R.layout.sample, null)/* or pass your view object*/);

2

Mam nadzieję że to pomoże

View view="some view instance";        
view.setDrawingCacheEnabled(true);
Bitmap bitmap=view.getDrawingCache();
view.setDrawingCacheEnabled(false);


getDrawingCache()Metoda aktualizacji jest przestarzała na poziomie API 28. Poszukaj więc innej alternatywy dla poziomu API> 28.


2
getDrawingCachejest obecnie przestarzały.
David Miguel,

1

Myślę, że to jest trochę lepsze:

/**
 * draws the view's content to a bitmap. code initially based on :
 * http://nadavfima.com/android-snippet-inflate-a-layout-draw-to-a-bitmap/
 */
@Nullable
public static Bitmap drawToBitmap(final View viewToDrawFrom, int width, int height) {
    boolean wasDrawingCacheEnabled = viewToDrawFrom.isDrawingCacheEnabled();
    if (!wasDrawingCacheEnabled)
        viewToDrawFrom.setDrawingCacheEnabled(true);
    if (width <= 0 || height <= 0) {
        if (viewToDrawFrom.getWidth() <= 0 || viewToDrawFrom.getHeight() <= 0) {
            viewToDrawFrom.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            width = viewToDrawFrom.getMeasuredWidth();
            height = viewToDrawFrom.getMeasuredHeight();
        }
        if (width <= 0 || height <= 0) {
            final Bitmap bmp = viewToDrawFrom.getDrawingCache();
            final Bitmap result = bmp == null ? null : Bitmap.createBitmap(bmp);
            if (!wasDrawingCacheEnabled)
                viewToDrawFrom.setDrawingCacheEnabled(false);
            return result;
        }
        viewToDrawFrom.layout(0, 0, width, height);
    } else {
        viewToDrawFrom.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        viewToDrawFrom.layout(0, 0, viewToDrawFrom.getMeasuredWidth(), viewToDrawFrom.getMeasuredHeight());
    }
    final Bitmap drawingCache = viewToDrawFrom.getDrawingCache();
    final Bitmap bmp = ThumbnailUtils.extractThumbnail(drawingCache, width, height);
    final Bitmap result = bmp == null || bmp != drawingCache ? bmp : Bitmap.createBitmap(bmp);
    if (!wasDrawingCacheEnabled)
        viewToDrawFrom.setDrawingCacheEnabled(false);
    return result;
}

Używając powyższego kodu, nie musisz określać rozmiaru bitmapy (użyj 0 dla szerokości i wysokości), jeśli chcesz użyć jednego z samego widoku.

Ponadto, jeśli chcesz przekonwertować specjalne widoki (na przykład SurfaceView, Surface lub Window) na mapę bitową, powinieneś zamiast tego rozważyć użycie klasy PixelCopy . Wymaga jednak API 24 i nowszych. Wcześniej nie wiem, jak to zrobić.


Każdy pomysł, żaden TextView nie jest dodawany do mapy bitowej. Dodawane są tylko ImageViews.
Khemraj

@Khemraj Nie rozumiem pytania.
programista Androida

To była moja wina, że ​​mojego TextView nie było w mapie bitowej. Ponieważ zastosowałem motyw w jasnych kolorach, dzięki za odpowiedź.
Khemraj

1
@Khemraj Przepraszam, ale nadal nie rozumiem. Wszystko w porządku teraz?
programista Androida

Tak bracie, nie wiem, dlaczego mnie nie dostajesz :). Miałem jeden TextView w układzie, który chciałem przekonwertować na Bitmap. Układ miał jeden ImageView i jeden TextView. ImageView był konwertowany na Bitmap. Ale TextView nie pojawiał się w Bitmap. To był problem. Po tym zdałem sobie sprawę, że zastosowałem motyw, który sprawiał, że tekst TextView był biały. Naprawiłem to. I teraz wszystko w porządku. Dzięki.
Khemraj

-3
view.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);

Proszę wyjaśnij, co to robi.
Paul Floyd,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.