ImageViewスケーリングTOP_CROP


88

私はImageViewデバイスの縦横比よりも大きなアスペクト比のpngを表示しているものを持っています(垂直方向に言えば、より長いことを意味します)。アスペクト比を維持し、親の幅を一致させ、imageviewを画面の上部に固定して表示します。

CENTER_CROPスケールタイプとして使用する際の問題は、画像ビューの上端を上端に揃えるのではなく、スケーリングされた画像を(理解できる)中央に配置することです。

の問題FIT_STARTは、画像が画面の高さに収まり、幅いっぱいにならないことです。

この問題は、カスタムImageViewをonDraw(Canvas)使用し、キャンバスを使用して手動でオーバーライドおよびハンドリングすることで解決しました。このアプローチの問題は、1)より簡単な解決策があるのではないかと心配しています。2)super(AttributeSet)ヒープに3 MBの空きがあるときに、src imgを330kbに設定しようとすると、コンストラクターでVM mem例外が発生します。 (ヒープサイズが6 mbの場合)、理由がわかりません。

どんなアイデア/提案/解決策も大歓迎です:)

ありがとう

PS私は解決策がマトリックススケールタイプを使用してそれを自分で行うことであるかもしれないと思ったが、それは私の現在の解決策と同じかそれ以上の仕事のようです!


1
CENTER_CROPを試し、ImageViewでadjustViewBoundsプロパティをtrueに設定しましたか?
PravinCG

2
はい、私はそのthxを試してみましたが、親よりも幅が広くなるまでビューを拡大し、それが画面よりも大きくならないようにし、画像を画面の中央に配置します。と下部
ドリ

回答:


84

わかりました、私には実用的な解決策があります。Darkoからのプロンプトにより、ImageViewクラス(ありがとう)をもう一度確認し、Matrixを使用して変換を適用しました(最初は疑っていたが、最初の試行で成功しなかった!)私のカスタムimageViewクラスでは、コンストラクターの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」メソッドを繰り返した理由がわからないだけです...私は最初の1つだけを使用して成功しました(そして2番目のxDを完全に無視しました)
Alesqui

3
xmlレイアウトを介してこれと2時間戦った後、これはうまくいきました。もっとボートをあげられたらいいのに。
Mark Beaton、2012

5
私は、体がそうでない画像が再描画することなく、表示されませんでした前に、スーパー()を呼び出す必要があった
sherpya

1
@VitorHugoSchwaabあなたはmatrix.postTranslate(..)のようなthmsを使用する必要があります
アントンキゼマ

1
バックグラウンドを使用できませんか?srcのみを使用しますか?
Egos Zhang

43

これが下部の中央に配置するための私のコードです。ところで 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)代わりに使用することはできないのではないでしょうか?
ドリ

確かに線if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))を逆にすべきですか?つまり、画像がフレームより大きいかどうかをテストしてはいけませんか?置き換えることをお勧めしますif((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
Carlos P

私が使用して親から幅を選んだ((View) getParent()).getWidth()ので、ImageView持っているMATCH_PARENT
sherpya

@ジェイそれは素晴らしい作品。私はonLayout()メソッドで行列計算を行っています。これは、回転時にも呼び出されますが、setFrameはそうではありません。
ボックス

このためおかげで、完璧に動作し、またすぐにコードの1行をコメントにし、トップ作物に下の作物を変更する方法を与えてくれてありがとう
Moonbloom

39

TOP_CROP機能を利用するためにカスタム画像ビューを作成する必要はありません。あなただけ変更する必要があるmatrixのをImageView

  1. 設定scaleTypeするmatrixために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未満の場合は確認する必要があります。それ以外の場合は単にに変更します。そうscaleTypecenterCropないと、エッジに空白スペースが表示されます。
2017年

下揃えの画像が必要な場合にこれを機能させるにはどうすればよいですか?
Sagar

26

この例は、オブジェクトの作成+いくつかの最適化の後にロードされたイメージで機能します。何が起こっているのかを説明するコメントをコードに追加しました。

電話することを忘れないでください:

imageView.setScaleType(ImageView.ScaleType.MATRIX);

または

android:scaleType="matrix"

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

}

下揃えの画像が必要な場合にこれを機能させるにはどうすればよいですか?
Sagar

13

ドリに基づいて、常に周囲のコンテナーを満たすために、画像の幅または高さに基づいて画像をスケーリングするソリューションを使用しています。これにより、中心を原点(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

水平方向または垂直方向の任意のクロップをサポートするクラスが必要であり、クロップを動的に変更できるようにしたかったため、これらのソリューションはどれも私にとってうまくいきませんでした。私はピカソの互換性も必要でした、そしてピカソは画像のドローアブルを遅延して設定します。

私の実装は、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();

カスタムの中央上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 14

computMatrix:ここで任意の行列を実行できます。
tianxia 14

onLayoutとても助かります!ありがとう!マトリックスを計算するがすぐに画像を表示せずonLayout、コードを追加すると問題が解決するという問題が発生しました。
natsumiyu 2016年

1

Fresco(SimpleDraweeView)を使用している場合は、次の方法で簡単に実行できます。

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

これは最高の作物です。

詳細はリファレンスリンクをご覧ください


0

ここでの解決策には2つの問題があります。

  • 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 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.