Escalonamento de ImageView TOP_CROP

88

Eu tenho um ImageViewque está exibindo um png que tem uma proporção de aspecto maior do que a do dispositivo (verticalmente falando - o que significa que é mais longo). Desejo exibir isso mantendo a proporção da imagem, combinando a largura do pai e fixando a visualização da imagem no topo da tela.

O problema que tenho ao usar CENTER_CROPcomo tipo de escala é que ele irá (compreensível) centralizar a imagem dimensionada em vez de alinhar a borda superior com a borda superior da visualização da imagem.

O problema FIT_STARTé que a imagem se ajusta à altura da tela e não ocupa a largura.

Resolvi esse problema usando um ImageView personalizado e substituindo onDraw(Canvas)e manipulando isso manualmente usando a tela; o problema com essa abordagem é que 1) Estou preocupado que possa haver uma solução mais simples, 2) Estou recebendo uma exceção VM mem ao chamarsuper(AttributeSet) o construtor ao tentar definir um src img de 330kb quando o heap tem 3 MB livres (com um tamanho de heap de 6 MB) e não consigo descobrir o porquê.

Quaisquer ideias / sugestões / soluções são bem-vindas :)

obrigado

ps pensei que uma solução pode ser usar um tipo de escala de matriz e fazer eu mesmo, mas isso parece ser o mesmo ou mais trabalho do que minha solução atual!

Dori
fonte
1
Você tentou com CENTER_CROP e definiu a propriedade AdjustViewBounds como verdadeira com o ImageView?
PravinCG
2
Sim, eu tentei isso, sem sucesso, estou com medo, pois vai expandir a visão até a largura de seu pai, que não será maior que a tela, e então centralizar a imagem na tela com o excesso de altura / 2 saindo do topo e inferior
Dori

Respostas:

84

Ok, eu tenho uma solução de trabalho. O prompt de Darko me fez olhar novamente para a classe ImageView (obrigado) e aplicar a transformação usando uma Matrix (como eu suspeitei originalmente, mas não tive sucesso na minha primeira tentativa!). Na minha classe imageView personalizada, chamo setScaleType(ScaleType.MATRIX)after super()no construtor e tenho o método a seguir.

    @Override
    protected boolean setFrame(int l, int t, int r, int b)
    {
        Matrix matrix = getImageMatrix(); 
        float scaleFactor = getWidth()/(float)getDrawable().getIntrinsicWidth();    
        matrix.setScale(scaleFactor, scaleFactor, 0, 0);
        setImageMatrix(matrix);
        return super.setFrame(l, t, r, b);
    }

Eu coloquei int no setFrame()método como em ImageView a chamada para configureBounds()está dentro deste método, que é onde todas as coisas de dimensionamento e matriz acontecem, então me parece lógico (digamos, se você discordar)

Abaixo está o método super.setFrame () do AOSP

 @Override
    protected boolean setFrame(int l, int t, int r, int b) {
        boolean changed = super.setFrame(l, t, r, b);
        mHaveFrame = true;
        configureBounds();
        return changed;
    }

Encontre a classe src completa aqui

Dori
fonte
Obrigado pelo código, @doridori! Funcionou ok! Só não entendo por que você repetiu o método "setFrame" em sua explicação ... Usei apenas o primeiro com sucesso (e ignorei completamente o segundo xD)
Alesqui
3
Depois de lutar com esse layout via xml por duas horas, funcionou. Eu gostaria de poder lhe dar mais subidas.
Mark Beaton
5
Tive que chamar super () antes do corpo, caso contrário a imagem não seria exibida sem uma repintura
sherpya
1
@VitorHugoSchwaab você tem que usar thms como matrix.postTranslate (..)
Anton Kizema
1
não pode usar fundo? usar apenas src?
Egos Zhang
43

aqui está meu código para centralizá-lo na parte inferior. Btw. no código de Dori há um pequeno bug: como o super.frame()é chamado no final, o getWidth()método pode retornar o valor errado. Se você quiser centralizá-lo no topo, simplesmente remova a linha postTranslate e pronto. O bom é que, com esse código, você pode movê-lo para onde quiser. (direita, centro => sem problemas;)

    public class CenterBottomImageView extends ImageView {

        public CenterBottomImageView(Context context) {
            super(context);
            setup();
        }

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

        public CenterBottomImageView(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) {
            if (getDrawable() == null) {
                return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
            }
            float frameWidth = frameRight - frameLeft;
            float frameHeight = frameBottom - frameTop;

            float originalImageWidth = (float)getDrawable().getIntrinsicWidth();
            float originalImageHeight = (float)getDrawable().getIntrinsicHeight();

            float usedScaleFactor = 1;

            if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight)) {
                // If frame is bigger than image
                // => Crop it, keep aspect ratio and position it at the bottom and center horizontally

                float fitHorizontallyScaleFactor = frameWidth/originalImageWidth;
                float fitVerticallyScaleFactor = frameHeight/originalImageHeight;

                usedScaleFactor = Math.max(fitHorizontallyScaleFactor, fitVerticallyScaleFactor);
            }

            float newImageWidth = originalImageWidth * usedScaleFactor;
            float newImageHeight = originalImageHeight * usedScaleFactor;

            Matrix matrix = getImageMatrix();
            matrix.setScale(usedScaleFactor, usedScaleFactor, 0, 0); // Replaces the old matrix completly
//comment matrix.postTranslate if you want crop from TOP
            matrix.postTranslate((frameWidth - newImageWidth) /2, frameHeight - newImageHeight);
            setImageMatrix(matrix);
            return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
        }

    }
MajorBreakfasts
fonte
Ótimo, obrigado por apontar o bug. Eu não toquei neste código por um tempo, então esta pode ser uma sugestão estúpida, mas para consertar o bug setWidth que você apontou, não poderíamos simplesmente usar (r-l)?
Dori
Certamente a linha if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))deve ser invertida? Em outras palavras, você não deveria testar se a imagem é maior do que o quadro? Sugiro substituí-lo porif((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
Carlos P
Eu escolhi a largura do pai usando ((View) getParent()).getWidth()desde o ImageViewhasMATCH_PARENT
sherpya
@Jay funciona muito bem. Estou fazendo cálculos de matriz no método onLayout (), que é chamado na rotação também, enquanto setFrame não.
caixa
Obrigado por isso, funciona perfeitamente, e também obrigado por fornecer uma maneira de mudar rapidamente o corte inferior para o corte superior comentando 1 linha de código
Moonbloom
39

Você não precisa escrever uma Visualização de imagem personalizada para obter a TOP_CROPfuncionalidade. Você só precisa modificar o matrixdo ImageView.

  1. Defina scaleTypecomo matrixpara ImageView:

    <ImageView
          android:id="@+id/imageView"
          android:contentDescription="Image"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:src="@drawable/image"
          android:scaleType="matrix"/>
  2. Defina uma matriz personalizada para ImageView:

    final ImageView imageView = (ImageView) findViewById(R.id.imageView);
    final Matrix matrix = imageView.getImageMatrix();
    final float imageWidth = imageView.getDrawable().getIntrinsicWidth();
    final int screenWidth = getResources().getDisplayMetrics().widthPixels;
    final float scaleRatio = screenWidth / imageWidth;
    matrix.postScale(scaleRatio, scaleRatio);
    imageView.setImageMatrix(matrix);

Isso lhe dará a TOP_CROPfuncionalidade.

Henry
fonte
2
Isso funcionou para mim. No entanto, tenho que verificar scaleRation se for <1, ​​então apenas altero scaleTypepara, centerCropcaso contrário, verei um espaço em branco nas bordas.
Dia
Como fazer isso funcionar nos casos em que há necessidade de imagens alinhadas na parte inferior?
Sagar
26

Este exemplo trabalha com imagens que são carregadas após a criação do objeto + alguma otimização. Eu adicionei alguns comentários no código que explicam o que está acontecendo.

Lembre-se de ligar para:

imageView.setScaleType(ImageView.ScaleType.MATRIX);

ou

android:scaleType="matrix"

Fonte Java:

import com.appunite.imageview.OverlayImageView;

public class TopAlignedImageView extends ImageView {
    private Matrix mMatrix;
    private boolean mHasFrame;

    @SuppressWarnings("UnusedDeclaration")
    public TopAlignedImageView(Context context) {
        this(context, null, 0);
    }

    @SuppressWarnings("UnusedDeclaration")
    public TopAlignedImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    @SuppressWarnings("UnusedDeclaration")
    public TopAlignedImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mHasFrame = false;
        mMatrix = new Matrix();
        // we have to use own matrix because:
        // ImageView.setImageMatrix(Matrix matrix) will not call
        // configureBounds(); invalidate(); because we will operate on ImageView object
    }

    @Override
    protected boolean setFrame(int l, int t, int r, int b)
    {
        boolean changed = super.setFrame(l, t, r, b);
        if (changed) {
            mHasFrame = true;
            // we do not want to call this method if nothing changed
            setupScaleMatrix(r-l, b-t);
        }
        return changed;
    }

    private void setupScaleMatrix(int width, int height) {
        if (!mHasFrame) {
            // we have to ensure that we already have frame
            // called and have width and height
            return;
        }
        final Drawable drawable = getDrawable();
        if (drawable == null) {
            // we have to check if drawable is null because
            // when not initialized at startup drawable we can
            // rise NullPointerException
            return;
        }
        Matrix matrix = mMatrix;
        final int intrinsicWidth = drawable.getIntrinsicWidth();
        final int intrinsicHeight = drawable.getIntrinsicHeight();

        float factorWidth = width/(float) intrinsicWidth;
        float factorHeight = height/(float) intrinsicHeight;
        float factor = Math.max(factorHeight, factorWidth);

        // there magic happen and can be adjusted to current
        // needs
        matrix.setTranslate(-intrinsicWidth/2.0f, 0);
        matrix.postScale(factor, factor, 0, 0);
        matrix.postTranslate(width/2.0f, 0);
        setImageMatrix(matrix);
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        // We have to recalculate image after chaning image
        setupScaleMatrix(getWidth(), getHeight());
    }

    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        // We have to recalculate image after chaning image
        setupScaleMatrix(getWidth(), getHeight());
    }

    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        // We have to recalculate image after chaning image
        setupScaleMatrix(getWidth(), getHeight());
    }

    // We do not have to overide setImageBitmap because it calls 
    // setImageDrawable method

}
Jacek Marchwicki
fonte
Como fazer isso funcionar nos casos em que há necessidade de imagens alinhadas na parte inferior?
Sagar
13

Com base em Dori, estou usando uma solução que dimensiona a imagem com base na largura ou altura da imagem para sempre preencher o contêiner circundante. Isso permite dimensionar uma imagem para preencher todo o espaço disponível usando o ponto superior esquerdo da imagem em vez do centro como origem (CENTER_CROP):

@Override
protected boolean setFrame(int l, int t, int r, int b)
{

    Matrix matrix = getImageMatrix(); 
    float scaleFactor, scaleFactorWidth, scaleFactorHeight;
    scaleFactorWidth = (float)width/(float)getDrawable().getIntrinsicWidth();
    scaleFactorHeight = (float)height/(float)getDrawable().getIntrinsicHeight();    

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

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

    return super.setFrame(l, t, r, b);
}

Espero que isso ajude - funciona como um mimo no meu projeto.

Matt
fonte
11
Esta é a melhor solução ... E adicione: float width = r - l; altura do flutuador = b - t;
Geltrude de
9

Nenhuma dessas soluções funcionou para mim, porque eu queria uma classe que suportasse um corte arbitrário na direção horizontal ou vertical e queria que me permitisse alterar o corte dinamicamente. Eu também precisava da compatibilidade do Picasso , e o Picasso define drawables de imagem preguiçosamente.

Minha implementação é adaptada diretamente de ImageView.java no AOSP. Para usá-lo, declare assim em XML:

    <com.yourapp.PercentageCropImageView
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="matrix"/>

Da fonte, se você deseja ter um corte superior, chame:

imageView.setCropYCenterOffsetPct(0f);

Se você deseja ter um corte inferior, chame:

imageView.setCropYCenterOffsetPct(1.0f);

Se você deseja ter um corte 1/3 do caminho para baixo, ligue:

imageView.setCropYCenterOffsetPct(0.33f);

Além disso, se você optar por usar outro método de corte, como fit_center, poderá fazê-lo e nenhuma lógica personalizada será acionada. (Outras implementações SÓ permitem que você use seus métodos de recorte).

Por último, adicionei um método, redraw (), portanto, se você decidir alterar seu método de corte / scaleType dinamicamente no código, poderá forçar o redesenho da visualização. Por exemplo:

fullsizeImageView.setScaleType(ScaleType.FIT_CENTER);
fullsizeImageView.redraw();

Para voltar ao seu terceiro corte central superior personalizado, chame:

fullsizeImageView.setScaleType(ScaleType.MATRIX);
fullsizeImageView.redraw();

Aqui está a aula:

/* 
 * Adapted from ImageView code at: 
 * http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.4_r1/android/widget/ImageView.java
 */
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class PercentageCropImageView extends ImageView{

    private Float mCropYCenterOffsetPct;
    private Float mCropXCenterOffsetPct;

    public PercentageCropImageView(Context context) {
        super(context);
    }

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

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

    public float getCropYCenterOffsetPct() {
        return mCropYCenterOffsetPct;
    }

    public void setCropYCenterOffsetPct(float cropYCenterOffsetPct) {
        if (cropYCenterOffsetPct > 1.0) {
            throw new IllegalArgumentException("Value too large: Must be <= 1.0");
        }
        this.mCropYCenterOffsetPct = cropYCenterOffsetPct;
    }

    public float getCropXCenterOffsetPct() {
        return mCropXCenterOffsetPct;
    }

    public void setCropXCenterOffsetPct(float cropXCenterOffsetPct) {
        if (cropXCenterOffsetPct > 1.0) {
            throw new IllegalArgumentException("Value too large: Must be <= 1.0");
        }
        this.mCropXCenterOffsetPct = cropXCenterOffsetPct;
    }

    private void myConfigureBounds() {
        if (this.getScaleType() == ScaleType.MATRIX) {
            /*
             * Taken from Android's ImageView.java implementation:
             * 
             * Excerpt from their source:
    } else if (ScaleType.CENTER_CROP == mScaleType) {
       mDrawMatrix = mMatrix;

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

       mDrawMatrix.setScale(scale, scale);
       mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
    }
             */

            Drawable d = this.getDrawable();
            if (d != null) {
                int dwidth = d.getIntrinsicWidth();
                int dheight = d.getIntrinsicHeight();

                Matrix m = new Matrix();

                int vwidth = getWidth() - this.getPaddingLeft() - this.getPaddingRight();
                int vheight = getHeight() - this.getPaddingTop() - this.getPaddingBottom();

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

                if (dwidth * vheight > vwidth * dheight) {
                    float cropXCenterOffsetPct = mCropXCenterOffsetPct != null ? 
                            mCropXCenterOffsetPct.floatValue() : 0.5f;
                    scale = (float) vheight / (float) dheight;
                    dx = (vwidth - dwidth * scale) * cropXCenterOffsetPct;
                } else {
                    float cropYCenterOffsetPct = mCropYCenterOffsetPct != null ? 
                            mCropYCenterOffsetPct.floatValue() : 0f;

                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * cropYCenterOffsetPct;
                }

                m.setScale(scale, scale);
                m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));

                this.setImageMatrix(m);
            }
        }
    }

    // These 3 methods call configureBounds in ImageView.java class, which
    // adjusts the matrix in a call to center_crop (android's built-in 
    // scaling and centering crop method). We also want to trigger
    // in the same place, but using our own matrix, which is then set
    // directly at line 588 of ImageView.java and then copied over
    // as the draw matrix at line 942 of ImageVeiw.java
    @Override
    protected boolean setFrame(int l, int t, int r, int b) {
        boolean changed = super.setFrame(l, t, r, b);
        this.myConfigureBounds();
        return changed;
    }
    @Override
    public void setImageDrawable(Drawable d) {          
        super.setImageDrawable(d);
        this.myConfigureBounds();
    }
    @Override
    public void setImageResource(int resId) {           
        super.setImageResource(resId);
        this.myConfigureBounds();
    }

    public void redraw() {
        Drawable d = this.getDrawable();

        if (d != null) {
            // Force toggle to recalculate our bounds
            this.setImageDrawable(null);
            this.setImageDrawable(d);
        }
    }
}
Esilver
fonte
5

Talvez vá para o código-fonte para a visualização da imagem no Android e veja como ele desenha o corte central etc. e talvez copie parte desse código em seus métodos. Eu realmente não sei por uma solução melhor do que fazer isso. Tenho experiência em redimensionar e cortar manualmente o bitmap (pesquisa por transformações de bitmap), o que reduz seu tamanho real, mas ainda cria um pouco de sobrecarga no processo.

DArkO
fonte
3
public class ImageViewTopCrop extends ImageView {
public ImageViewTopCrop(Context context) {
    super(context);
    setScaleType(ScaleType.MATRIX);
}

public ImageViewTopCrop(Context context, AttributeSet attrs) {
    super(context, attrs);
    setScaleType(ScaleType.MATRIX);
}

public ImageViewTopCrop(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setScaleType(ScaleType.MATRIX);
}

@Override
protected boolean setFrame(int l, int t, int r, int b) {
    computMatrix();
    return super.setFrame(l, t, r, b);
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    computMatrix();
}

private void computMatrix() {
    Matrix matrix = getImageMatrix();
    float scaleFactor = getWidth() / (float) getDrawable().getIntrinsicWidth();
    matrix.setScale(scaleFactor, scaleFactor, 0, 0);
    setImageMatrix(matrix);
}

}

tianxia
fonte
setFrame && onLayout
tianxia
computMatrix: você pode fazer qualquer matriz aqui.
tianxia
onLayoutme salva muito! Obrigado! Eu encontrei um problema onde ele calcula a matriz, mas não exibe a imagem imediatamente e adicionar onLayouto código resolve meu problema.
natsumiyu
1

Se você estiver usando Fresco (SimpleDraweeView), você pode fazer isso facilmente com:

 PointF focusPoint = new PointF(0.5f, 0f);
 imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);

Este seria para uma safra superior.

Mais informações no link de referência

Fxhereng
fonte
0

Existem 2 problemas com as soluções aqui:

  • Eles não são renderizados no editor de layout do Android Studio (para que você possa visualizar em vários tamanhos de tela e proporções)
  • Ele é dimensionado apenas de acordo com a largura, portanto, dependendo das proporções do dispositivo e da imagem, você pode acabar com uma tira vazia na parte inferior

Esta pequena modificação corrige o problema (coloque o código no onDraw e verifique os fatores de escala de largura e altura):

@Override
protected void onDraw(Canvas canvas) {

    Matrix matrix = getImageMatrix();

    float scaleFactorWidth = getWidth() / (float) getDrawable().getIntrinsicWidth();
    float scaleFactorHeight = getHeight() / (float) getDrawable().getIntrinsicHeight();

    float scaleFactor = (scaleFactorWidth > scaleFactorHeight) ? scaleFactorWidth : scaleFactorHeight;

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

    super.onDraw(canvas);
}
OldSchool4664
fonte
-1

Solução mais simples: corte a imagem

 @Override
    public void draw(Canvas canvas) {
        if(getWidth() > 0){
            int clipHeight = 250;
            canvas.clipRect(0,clipHeight,getWidth(),getHeight());
         }
        super.draw(canvas);
    }
M. Usman Khan
fonte
Isso não aumentará a escala da imagem se ela for menor do que a visualização, razão pela qual as outras soluções não são tão simples.
OldSchool4664