[android] ImageView 배율 TOP_CROP

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

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

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

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

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

감사

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



답변

좋아, 작동하는 솔루션이 있습니다. 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 찾기


답변

여기 중앙에 배치하는 코드가 있습니다. 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);
        }

    }


답변

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기능 이 제공 됩니다.


답변

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

다음으로 전화하십시오.

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

}


답변

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

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


답변

수평 또는 수직 방향에서 임의의 자르기를 지원하는 클래스를 원했기 때문에 이러한 솔루션 중 어느 것도 나에게 적합하지 않았으며 자르기를 동적으로 변경할 수 있기를 원했습니다. 또한 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);
        }
    }
}


답변

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