ImageView 배율 TOP_CROP


88

나는 ImageView장치의 종횡비보다 더 큰 종횡비를 가진 png를 표시하고 있습니다 (수직으로 말하면-더 길다는 것을 의미합니다). 가로 세로 비율을 유지하고 부모의 너비를 일치시키고 이미지 뷰를 화면 상단에 고정하면서 이것을 표시하고 싶습니다.

CENTER_CROP배율 유형 으로 사용하는 문제 는 이미지보기의 위쪽 가장자리를 위쪽 가장자리에 정렬하는 대신 배율 조정 된 이미지를 중앙에 배치한다는 것입니다 (이해할 수 있음).

문제 FIT_START는 이미지가 화면 높이에 맞고 너비를 채우지 않는다는 것입니다.

사용자 지정 ImageView를 onDraw(Canvas)사용하고 캔버스를 사용하여 수동으로 재정의 및 처리 하여이 문제를 해결했습니다 . 이 접근 방식의 문제는 1) 더 간단한 솔루션이 있을지 걱정됩니다. 2) super(AttributeSet)힙에 3MB의 여유 공간이있을 때 src img를 330kb로 설정하려고 할 때 생성자에서 호출 할 때 VM mem 예외가 발생합니다. (힙 크기가 6MB) 이유를 알 수 없습니다.

모든 아이디어 / 제안 / 솔루션을 환영합니다 :)

감사

추신 나는 솔루션이 매트릭스 스케일 유형을 사용하고 직접 수행하는 것이라고 생각했지만 현재 솔루션과 동일하거나 더 많은 작업 인 것 같습니다!


1
CENTER_CROP로 시도하고 ImageView에서 adjustViewBounds 속성을 true로 설정 했습니까?
PravinCG 2011-06-13

2
예, 나는 그것을 시도했지만 성공하지 못했습니다. 부모 너비가 화면보다 크지 않을 때까지보기를 확장 한 다음 초과 높이 / 2가 상단을 찌르는 화면 중앙에 이미지를 배치합니다. 및 바닥
Dori

답변:


84

좋아, 작동하는 솔루션이 있습니다. Darko의 프롬프트를 통해 ImageView 클래스를 다시 살펴보고 (감사합니다) Matrix를 사용하여 변환을 적용했습니다 (원래 예상했던 것처럼 첫 번째 시도에서 성공하지 못했습니다!). 내 사용자 지정 이미지 뷰 클래스에서 나는 전화 setScaleType(ScaleType.MATRIX)super()생성자에서 다음과 같은 방법이있다.

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

setFrame()ImageView 에서와 같이 메서드 에 int를 배치했습니다.이 메서드에 대한 호출 configureBounds()은 모든 크기 조정 및 매트릭스 작업이 발생하는 곳이므로 논리적으로 보입니다 (동의하지 않는 경우)

아래는 AOSP의 super.setFrame () 메서드입니다.

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

여기 에서 전체 클래스 src 찾기


코드 감사합니다, @doridori! 괜찮 았어요! 당신이 당신의 설명에서 "setFrame"방법을 반복 않았는지 난 그냥 성공 단지 첫 번째를 사용 (완전히 두 번째의 xD 무시) ... 이해가 안 돼요
Alesqui

3
두 시간 동안 xml 레이아웃을 통해이 작업을 수행 한 후 작동했습니다. 더 많은 배를 줄 수 있으면 좋겠어요.
Mark Beaton 2012 년

5
나는 몸이 그렇지 않으면 이미지가 다시 그리기없이 표시되지 것 전에 슈퍼 ()를 호출했다
sherpya

1
당신은 (..) matrix.postTranslate 같은 사용 thms에있는 @VitorHugoSchwaab
안톤 Kizema

1
배경을 사용할 수 없습니까? src 만 사용합니까?
Egos Zhang

43

여기 중앙에 배치하는 코드가 있습니다. Btw. Dori의 코드에는 약간의 버그가 있습니다. super.frame()맨 끝에가 호출되므로 getWidth()메서드가 잘못된 값을 반환 할 수 있습니다. 상단 중앙에 배치하려면 postTranslate 줄을 제거하기 만하면됩니다. 좋은 점은이 코드를 사용하여 원하는 곳으로 이동할 수 있다는 것입니다. (오른쪽, 가운데 => 문제 없음;)

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

    }

좋습니다. 버그를 지적 해 주셔서 감사합니다. 이 코드를 한동안 건드리지 않았으므로 이것은 어리석은 제안 일 수 있지만 지적한 setWidth 버그를 수정하려면 (r-l)대신 사용할 수 없습니까?
Dori

당연히 선 if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))을 반대로해야합니까? 즉, 이미지가 프레임보다 큰지 테스트해야하지 않습니까? 나는 그것을 교체하는 것이 좋습니다if((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
Carlos P

내가 사용하는 부모로부터 폭을 골랐다 ((View) getParent()).getWidth()(가)부터 ImageViewMATCH_PARENT
sherpya

@Jay 잘 작동합니다. onLayout () 메서드에서 행렬 계산을 수행합니다.이 메서드는 회전시에도 호출되지만 setFrame은 그렇지 않습니다.
box '

이 주셔서 감사합니다, 신속하게 코드의 한 라인을 주석으로 최고 작물에 바닥 작물을 변경하는 방법주는 또한 감사를 완벽하게 작동하고
Moonbloom

39

TOP_CROP기능 을 얻기 위해 사용자 정의 이미지보기를 작성할 필요가 없습니다 . 당신은 방금 수정해야 matrix의를 ImageView.

  1. 설정 scaleTypematrix에 대한 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. 에 대한 사용자 지정 행렬을 설정합니다 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);
    

이렇게하면 TOP_CROP기능 이 제공 됩니다.


2
이것은 나를 위해 일했습니다. 그러나 scaleRation이 1 미만인 경우 확인해야합니다. 그런 다음으로 변경합니다 scaleType. centerCrop그렇지 않으면 가장자리에 빈 공간이 표시됩니다.
Arst

아래쪽 정렬 된 이미지가 필요한 경우이 작업을 수행하는 방법은 무엇입니까?
Sagar

26

이 예제는 객체 생성 + 일부 최적화 후로드 된 이미지와 함께 작동합니다. 무슨 일이 일어나고 있는지 설명하는 코드에 주석을 추가했습니다.

다음으로 전화하십시오.

imageView.setScaleType(ImageView.ScaleType.MATRIX);

또는

android:scaleType="matrix"

자바 소스 :

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

}

아래쪽 정렬 된 이미지가 필요한 경우이 작업을 수행하는 방법은 무엇입니까?
Sagar

13

Dori를 기반으로 항상 주변 컨테이너를 채우기 위해 이미지의 너비 또는 높이를 기준으로 이미지의 크기를 조정하는 솔루션을 사용하고 있습니다. 이렇게하면 중심 (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);
}

이것이 도움이 되었기를 바랍니다. 내 프로젝트에서 대접처럼 작동합니다.


11
이것이 더 나은 솔루션입니다 ... 그리고 추가 : float width = r-l; 플로트 높이 = b-t;
Geltrude 2013 년

9

수평 또는 수직 방향에서 임의의 자르기를 지원하는 클래스를 원했기 때문에 이러한 솔루션 중 어느 것도 나에게 적합하지 않았으며 자르기를 동적으로 변경할 수 있기를 원했습니다. 또한 Picasso 호환성이 필요 했고 Picasso는 이미지 드로어 블을 느리게 설정했습니다.

내 구현은 AOSP의 ImageView.java 에서 직접 조정됩니다 . 이를 사용하려면 XML에서 다음과 같이 선언하십시오.

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

소스에서 최고 자르기를 원하면 다음으로 전화하십시오.

imageView.setCropYCenterOffsetPct(0f);

하단 자르기를 원하시면 다음으로 전화하십시오.

imageView.setCropYCenterOffsetPct(1.0f);

1/3 정도 자르려면 다음으로 전화하십시오.

imageView.setCropYCenterOffsetPct(0.33f);

또한 fit_center와 같은 다른 자르기 방법을 사용하기로 선택한 경우 그렇게 할 수 있으며이 사용자 지정 논리가 트리거되지 않습니다. (다른 구현에서는 자르기 방법 만 사용할 수 있습니다.)

마지막으로 redraw () 메서드를 추가 했으므로 코드에서 자르기 메서드 / scaleType을 동적으로 변경하도록 선택하면 뷰를 강제로 다시 그릴 수 있습니다. 예를 들면 :

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

사용자 정의 상단 중앙 1/3 자르기로 돌아가려면 다음으로 전화하십시오.

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

수업은 다음과 같습니다.

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

5

Android의 이미지보기에 대한 소스 코드로 이동하여 중앙 자르기 등을 그리는 방법을 확인하고 해당 코드 중 일부를 메서드에 복사 할 수 있습니다. 나는 이것을하는 것보다 더 나은 해결책을 정말로 모른다. 실제 크기를 줄이는 비트 맵 (비트 맵 변환 검색)의 크기를 수동으로 조정하고 자르는 경험이 있지만 여전히 프로세스에서 약간의 오버 헤드가 발생합니다.


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

}


setFrame && onLayout
tianxia

computMatrix : 여기서 모든 행렬을 수행 할 수 있습니다.
tianxia

onLayout저를 많이 구합니다! 감사! 행렬을 계산하지만 이미지를 즉시 표시하지 않고 onLayout코드를 추가하면 문제가 해결되는 문제가 발생했습니다.
natsumiyu

1

Fresco (SimpleDraweeView) 를 사용하는 경우 다음 과 같이 쉽게 할 수 있습니다.

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

이것은 최고의 작물을위한 것입니다.

참조 링크 에서 더 많은 정보


0

여기 솔루션에는 두 가지 문제가 있습니다.

  • Android Studio 레이아웃 편집기에서 렌더링되지 않습니다 (따라서 다양한 화면 크기 및 종횡비로 미리 볼 수 있음).
  • 너비로만 크기가 조정되므로 장치와 이미지의 가로 세로 비율에 따라 하단에 빈 줄이 생길 수 있습니다.

이 작은 수정으로 문제가 해결됩니다 (onDraw에 코드를 배치하고 너비 및 높이 배율을 확인).

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

-1

가장 간단한 솔루션 : 이미지 자르기

 @Override
    public void draw(Canvas canvas) {
        if(getWidth() > 0){
            int clipHeight = 250;
            canvas.clipRect(0,clipHeight,getWidth(),getHeight());
         }
        super.draw(canvas);
    }

보기보다 작 으면 이미지가 확대되지 않으므로 다른 솔루션이 그렇게 간단하지 않습니다.
OldSchool4664
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.