Last active
October 1, 2024 10:40
-
-
Save arriolac/3843346 to your computer and use it in GitHub Desktop.
Custom Android ImageView for top-crop scaling of the contained drawable.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
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?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is a Kolin version: