-
-
Save arriolac/3843346 to your computer and use it in GitHub Desktop.
import android.annotation.TargetApi; | |
import android.content.Context; | |
import android.graphics.Matrix; | |
import android.graphics.drawable.Drawable; | |
import android.os.Build; | |
import android.util.AttributeSet; | |
import android.widget.ImageView; | |
/** | |
* Created by chris on 7/27/16. | |
*/ | |
public class TopCropImageView extends ImageView { | |
public TopCropImageView(Context context) { | |
super(context); | |
init(); | |
} | |
public TopCropImageView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(); | |
} | |
public TopCropImageView(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
init(); | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
public TopCropImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | |
super(context, attrs, defStyleAttr, defStyleRes); | |
init(); | |
} | |
@Override | |
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | |
super.onLayout(changed, left, top, right, bottom); | |
recomputeImgMatrix(); | |
} | |
@Override | |
protected boolean setFrame(int l, int t, int r, int b) { | |
recomputeImgMatrix(); | |
return super.setFrame(l, t, r, b); | |
} | |
private void init() { | |
setScaleType(ScaleType.MATRIX); | |
} | |
private void recomputeImgMatrix() { | |
final Drawable drawable = getDrawable(); | |
if (drawable == null) { | |
return; | |
} | |
final Matrix matrix = getImageMatrix(); | |
float scale; | |
final int viewWidth = getWidth() - getPaddingLeft() - getPaddingRight(); | |
final int viewHeight = getHeight() - getPaddingTop() - getPaddingBottom(); | |
final int drawableWidth = drawable.getIntrinsicWidth(); | |
final int drawableHeight = drawable.getIntrinsicHeight(); | |
if (drawableWidth * viewHeight > drawableHeight * viewWidth) { | |
scale = (float) viewHeight / (float) drawableHeight; | |
} else { | |
scale = (float) viewWidth / (float) drawableWidth; | |
} | |
matrix.setScale(scale, scale); | |
setImageMatrix(matrix); | |
} | |
} |
Thanks! But it's will be better for using in xml with additional constructors:
public TopCropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.MATRIX);
}
public TopCropImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setScaleType(ScaleType.MATRIX);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TopCropImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setScaleType(ScaleType.MATRIX);
}
to left side add matrix.postTranslate(viewWidth - drawableWidth*scale,0);
after matrix.setScale(scale, scale);
when i use this class compile show this error
android.view.InflateException: Binary XML file
I'd love to get an explanation on how to use this in detail.
I've created a new file with this name and copied over the code. I then changed my ImageView to a TopCropImageView in XML, but I'm getting a litany of errors
Custom view TopCropImageView is not using the 2- or 3-argument View constructors; XML attributes will not work
java.lang.NullPointerException at line 35
Unable to start activity... android.view.InflateException: Binary XML file line #62: Binary XML file line #62: Error inflating class
add this code
First, great stuff! It works great! But, there's a but.. What I'm looking for is exactly the opposite...
How do I modify the code so that I'll have the same effect, but with the bottom of my image rather that it's top (as it works by default) ?
Never mind... Thanks to @MrVilkaman answer, I found the way.. Thanks dude!
So what you'll do to accomplish that, simply add at the bottom of the class, right after matrix.setScale(scale, scale);
, this line : matrix.postTranslate( 0, viewHeight - drawableHeight * scale);
.
Hope it will help someone else too..
Cheers!
It's been a while since I've updated this class which explains why there's some issues/warnings caused by newer APIs. I'll be posting an update to this code soon.
Updated gist to now work when inflated in XML and making sure that the drawable is not null before using it.
@arriolac
Thanks your code :)
Can I use your code in my commercial app?
Please let me know how I can use the code for commercial distribution.
Thanks for this.
final Matrix matrix = getImageMatrix();
any concern about the doc says "Do not change this matrix in place but make a copy.":
/** Returns the view's optional matrix. This is applied to the
view's drawable when it is drawn. If there is no matrix,
this method will return an identity matrix.
Do not change this matrix in place but make a copy.
If you want a different matrix applied to the drawable,
be sure to call setImageMatrix().
*/
public Matrix getImageMatrix() {
if (mDrawMatrix == null) {
return new Matrix(Matrix.IDENTITY_MATRIX);
}
return mDrawMatrix;
}
Image not loading after swipe to refresh layout in glide library.
Please give some feedback.
`public class TopCropImageView extends android.support.v7.widget.AppCompatImageView {
public TopCropImageView(Context context) {
super(context);
init();
}
public TopCropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TopCropImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TopCropImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
recomputeImgMatrix();
}
@Override
protected boolean setFrame(int l, int t, int r, int b) {
recomputeImgMatrix();
return super.setFrame(l, t, r, b);
}
private void init() {
setScaleType(ScaleType.MATRIX);
}
private void recomputeImgMatrix() {
Drawable drawable = getDrawable();
if (drawable != null) {
final Matrix matrix = getImageMatrix();
float scale;
final int viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
final int viewHeight = getHeight() - getPaddingTop() - getPaddingBottom();
final int drawableWidth = getDrawable().getIntrinsicWidth();
final int drawableHeight = getDrawable().getIntrinsicHeight();
if (drawableWidth * viewHeight > drawableHeight * viewWidth) {
scale = (float) viewHeight / (float) drawableHeight;
} else {
scale = (float) viewWidth / (float) drawableWidth;
}
matrix.setScale(scale, scale);
if ((drawableWidth * scale) > viewWidth) {
float tr = -(((drawableWidth * scale) - viewWidth) / 2);
matrix.postTranslate(tr, 0);
}
setImageMatrix(matrix);
}
}
}
`
please add this method on code
@Override protected void onDraw(Canvas canvas) { recomputeImgMatrix(); super.onDraw(canvas); }
Thanks @Dishant624 for your code
You save my day :)
@Dishant624 - could you elaborate why? It would be much more costly in terms of performance.
Also this was enough for me:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
recomputeImgMatrix();
}
Hence no need for overriding setFrame
method. Also, setFrame
is supported ONLY for two View
subclasses: https://stackoverflow.com/questions/4751963/android-why-cant-i-override-setframe-from-view
In Kotlin, I got succeeded with MotionLayout
with:
class BottomCenterImageView : AppCompatImageView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
init {
scaleType = ScaleType.MATRIX
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
recomputeImageMatrix()
}
private fun recomputeImageMatrix() {
val drawable = drawable ?: return
val viewWidth = width - paddingLeft - paddingRight
val viewHeight = height - paddingTop - paddingBottom
val drawableWidth = drawable.intrinsicWidth
val drawableHeight = drawable.intrinsicHeight
imageMatrix = imageMatrix.apply {
setTranslate(
Math.round((viewWidth - drawableWidth) * 0.5f).toFloat(),
Math.round((viewHeight - drawableHeight).toFloat()).toFloat()
)
}
}
}
Here is a Kolin version:
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
class TopCropImageView : AppCompatImageView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)
init {
scaleType = ScaleType.MATRIX
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
recomputeImgMatrix()
}
override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean {
recomputeImgMatrix()
return super.setFrame(l, t, r, b)
}
private fun recomputeImgMatrix() {
val matrix = imageMatrix
val viewWidth = width - paddingLeft - paddingRight
val viewHeight = height - paddingTop - paddingBottom
val drawableWidth = drawable.intrinsicWidth
val drawableHeight = drawable.intrinsicHeight
val scale = if (drawableWidth * viewHeight > drawableHeight * viewWidth) {
viewHeight.toFloat() / drawableHeight.toFloat()
} else {
viewWidth.toFloat() / drawableWidth.toFloat()
}
matrix.setScale(scale, scale)
imageMatrix = matrix
}
}
And here a version adapted to allow you to set the alignment in the range (0.0, 0.0)
= top left to (1.0, 1.0)
= (bottom right) either via code or XML attributes:
open class AlignmentCropImageView : AppCompatImageView {
constructor(context: Context) : super(context) {
initAttrs(context, null, 0)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initAttrs(context, attrs, 0)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
initAttrs(context, attrs, defStyleAttr)
}
open var alignmentX = 0.5f
open var alignmentY = 0.5f
private fun initAttrs(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
scaleType = ScaleType.MATRIX
context.obtainStyledAttributes(
attrs,
R.styleable.AlignmentCropImageView,
defStyleAttr,
0
).apply {
alignmentX = getFloat(R.styleable.AlignmentCropImageView_alignmentX, alignmentX)
alignmentY = getFloat(R.styleable.AlignmentCropImageView_alignmentX, alignmentY)
}.recycle()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
recomputeImgMatrix()
}
override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean {
recomputeImgMatrix()
return super.setFrame(l, t, r, b)
}
private fun recomputeImgMatrix() {
val matrix = imageMatrix
val viewWidth = width - paddingLeft - paddingRight
val viewHeight = height - paddingTop - paddingBottom
val drawableWidth = drawable?.intrinsicWidth ?: 0
val drawableHeight = drawable?.intrinsicHeight ?: 0
val scale = if (drawableWidth * viewHeight > drawableHeight * viewWidth) {
viewHeight.toFloat() / drawableHeight.toFloat()
} else {
viewWidth.toFloat() / drawableWidth.toFloat()
}
matrix.setScale(scale, scale)
matrix.postTranslate(
(viewWidth - drawableWidth * scale) * alignmentX,
(viewHeight - drawableHeight * scale) * alignmentY
)
imageMatrix = matrix
}
}
attrs.xml
:
<resources>
<declare-styleable name="AlignmentCropImageView">
<attr name="alignmentX" format="float" />
<attr name="alignmentY" format="float" />
</declare-styleable>
</resources>
This is for the top.
What if I want to make sure a specific point/rectangle is shown while fitting&cropping, keeping the aspect ratio?
How can I do it?
Currently the only similar thing that officially exists is center-crop, but it's only to the center. What if the most important part in the image is at the bottom, instead? Or 10% (or 10px) from the bottom, etc... ? Or if there is a specific region in the bitmap that's most important?
Worked perfectly in conjunction with UIL (universal image loader)