web-dev-qa-db-ja.com

ImageViewスケーリングTOP_CROP

ImageViewがあり、デバイスのアスペクト比よりも大きいアスペクト比のpngを表示しています(垂直方向-長いことを意味します)。アスペクト比を維持し、親の幅を一致させ、imageviewを画面の上部に固定しながら、これを表示したいと思います。

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

_FIT_START_の問題は、画像が画面の高さに適合し、幅に収まらないことです。

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

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

ありがとう

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

84
Dori

さて、私は実用的なソリューションを持っています。 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);
    }
_

ImageViewのようにsetFrame()メソッドに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を見つける here

83
Dori

下に中央揃えするためのコードがあります。ところで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);
        }

    }
40
MajorBreakfast

TOP_CROP機能を取得するためにカスタム画像ビューを作成する必要はありません。 matrixImageViewを変更するだけです。

  1. scaleTypematrixImageViewに設定します。

    <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機能が提供されます。

32
Henry

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

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

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

}
25

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

これが役立つことを願っています-私のプロジェクトの御treat走のように機能します。

13
Matt

水平方向または垂直方向の任意のクロップをサポートするクラスが必要であり、クロップを動的に変更できるようにしたかったため、これらのソリューションはどれも役に立ちませんでした。 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();

カスタムのトップセンター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);
        }
    }
}
9
esilver

Androidの画像ビューのソースコードに移動して、中央の切り抜きなどを描画する方法を確認してください。そのコードの一部をメソッドにコピーすることもできます。これを行うよりも優れたソリューション。私は手動でビットマップのサイズ変更とクロップ(ビットマップ変換の検索)の経験があり、実際のサイズは小さくなりますが、それでもプロセスに多少のオーバーヘッドが生じます。

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

}

3
tianxia

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

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

これはトップクロップ用です。

詳細は 参照リンク

1
fxhereng

このソリューションには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);
}
0
OldSchool4664