Crop camera preview for SurfaceView - android

I have a SurfaceView for barcode scan (by this tutorial).
<SurfaceView
android:id="#+id/camera_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
Need to set there cropped camera preview with 1/4 height of the screen.
I'm set a view size based on screen size:
cameraView = (SurfaceView) findViewById(R.id.camera_view);
final DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(metrics.widthPixels, metrics.heightPixels / 4);
cameraView.setLayoutParams(layoutParams);
But camera preview just shrinks inside!
Here is how i set it
final BarcodeDetector barcodeDetector =
new BarcodeDetector.Builder(this)
.setBarcodeFormats(Barcode.ALL_FORMATS)
.build();
final CameraSource cameraSource = new CameraSource
.Builder(this, barcodeDetector)
.setRequestedPreviewSize(metrics.widthPixels, metrics.heightPixels / 4)
.setAutoFocusEnabled(true)
.build();
cameraView.getHolder().addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
cameraSource.start(cameraView.getHolder());
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
cameraSource.stop();
}
});
How can i crop camera preview? Need just a bottom 1/4 part.
Also need to add a transparent layer with rectangle viewfinder over it.

What you are probably looking to use is a TextureView. SurfaceViews are not designed to be cropped - the video display adjusting to the size of the SurfaceView is the expected behaviour.
On the other hand, TextureViews, which are available from API level 14+, allow you to set a .setTransform(Matrix matrix), where the matrix contains data concerning how to scale the video, and .setTranslationX(float x) and .setTranslationY(float y) to crop the video. A good example is available here.
If you absolutely must use SurfaceView, I would recommend looking into using a GLSurfaceView with a custom renderer that scales the video. A good starting point would be here.

I solved it with a ViewGroup.
In layout:
<com.superup.smartshelf.barcode.BarcodeCameraPreview
android:id="#+id/preview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.25"/>
In class:
public class BarcodeCameraPreview extends ViewGroup {
private SurfaceView mSurfaceView;
public BarcodeCameraPreview(final Context context, AttributeSet attrs) {
super(context, attrs);
mSurfaceView = new SurfaceView(context);
mSurfaceView.getHolder().addCallback(new SurfaceCallback());
addView(mSurfaceView);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// crop view
int actualPreviewWidth = getResources().getDisplayMetrics().widthPixels;
int actualPreviewHeight = getResources().getDisplayMetrics().heightPixels;
if (mSurfaceView != null) {
mSurfaceView.layout(0, 0, actualPreviewWidth, actualPreviewHeight);
}
}
// camera methods
}
In activity:
BarcodeCameraPreview camera = (BarcodeCameraPreview) findViewById(R.id.preview);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(metrics.widthPixels, metrics.heightPixels / 4);
camera.setLayoutParams(layoutParams);

You cannot achieve crop by shrinking the view. You can overlay the camera preview with another layout and effectively hide the upper 3/4 of the picture.

Hi I have found the ultimate solution from one of the google Codelabs projects
https://codelabs.developers.google.com/codelabs/mlkit-android-translate?hl=en#0
// overlay is your surface view's id
overlay.apply {
setZOrderOnTop(true)
holder.setFormat(PixelFormat.TRANSPARENT)
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(
p0: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
}
override fun surfaceDestroyed(p0: SurfaceHolder) {
}
override fun surfaceCreated(p0: SurfaceHolder) {
holder?.let { drawOverlay(it, 65, 15) }
}
})
}
//--------------Below function is to draw the cropped overlay--------------
private fun drawOverlay(
holder: SurfaceHolder,
heightCropPercent: Int,
widthCropPercent: Int
) {
val canvas = holder.lockCanvas()
val bgPaint = Paint().apply {
alpha = 160
}
canvas.drawPaint(bgPaint)
val rectPaint = Paint()
rectPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
rectPaint.style = Paint.Style.FILL
rectPaint.color = Color.WHITE
val outlinePaint = Paint()
outlinePaint.style = Paint.Style.STROKE
outlinePaint.color = Color.WHITE
outlinePaint.strokeWidth = 4f
val surfaceWidth = holder.surfaceFrame.width()
val surfaceHeight = holder.surfaceFrame.height()
val cornerRadius = 25f
// Set rect centered in frame
val rectTop = surfaceHeight * heightCropPercent / 2 / 100f
val rectLeft = surfaceWidth * widthCropPercent / 2 / 100f
val rectRight = surfaceWidth * (1 - widthCropPercent / 2 / 100f)
val rectBottom = surfaceHeight * (1 - heightCropPercent / 2 / 100f)
val rect = RectF(rectLeft, rectTop, rectRight, rectBottom)
canvas.drawRoundRect(
rect, cornerRadius, cornerRadius, rectPaint
)
canvas.drawRoundRect(
rect, cornerRadius, cornerRadius, outlinePaint
)
val textPaint = Paint()
textPaint.color = Color.WHITE
textPaint.textSize = 50F
val overlayText = getString(R.string.overlay_help)
val textBounds = Rect()
textPaint.getTextBounds(overlayText, 0, overlayText.length, textBounds)
val textX = (surfaceWidth - textBounds.width()) / 2f
val textY = rectBottom + textBounds.height() + 15f // put text below rect and 15f padding
canvas.drawText(getString(R.string.overlay_help), textX, textY, textPaint)
holder.unlockCanvasAndPost(canvas)
}

Related

Can I give alpha attribute to LinearLayout but in some positions, Not to the whole of view?

I have LinearLayout, Can I give the alpha attribute to LinearLayout but in some positions, Not to the whole of view?
For example give android:alpha="0.5" to LinearLayout, Starting 150px to 300px ( The Width ) and 500px to 650px ( The Height )
There are likely a number of ways to approach this. What occurs to me is to write a custom drawable that will set the background the way you want.
MyBackgrondDrawable.kt (Kotlin version)
class MyBackgroundDrawable : Drawable() {
private val paint = Paint().apply {
style = Paint.Style.FILL
color = 0x0000ff
}
private var centerRect = Rect()
override fun draw(canvas: Canvas) {
canvas.withSave {
// Find the center of the canvas.
val centerWidth = (bounds.width() / 2)
val centerHeight = (bounds.height() / 2)
// Define our box around the center.
centerRect.left = centerWidth - HALF_BOX_SIZE
centerRect.top = centerHeight - HALF_BOX_SIZE
centerRect.right = centerWidth + HALF_BOX_SIZE
centerRect.bottom = centerHeight + HALF_BOX_SIZE
// Draw the paint with this alpha inside of the center box.
paint.alpha = 0x55
withClip(centerRect) {
drawPaint(paint)
}
// Invert the clipped region.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
clipOutRect(centerRect)
} else {
clipRect(centerRect, Region.Op.DIFFERENCE)
}
// Draw the paint with this alpha outside of the center box.
paint.alpha = 0xff
drawPaint(paint)
}
}
override fun setAlpha(alpha: Int) {
}
override fun setColorFilter(colorFilter: ColorFilter?) {
}
override fun getOpacity(): Int {
return PixelFormat.OPAQUE
}
private companion object {
const val BOX_SIZE = 150 // width & height in pixels
const val HALF_BOX_SIZE = BOX_SIZE / 2
}
}
MyBackgrondDrawable.java (Java version)
public class MyBackgroundDrawable extends Drawable {
private final Paint paint = new Paint();
private final RectF centerRect = new RectF();
MyBackgroundDrawableJava() {
paint.setStyle(Paint.Style.FILL);
paint.setColor(0x0000ff);
}
#Override
public void draw(Canvas canvas) {
canvas.save();
// Find the center of the canvas.
Rect bounds = getBounds();
float centerWidth = (bounds.width() / 2f);
float centerHeight = (bounds.height() / 2f);
// Define our box around the center.
centerRect.left = centerWidth - HALF_BOX_SIZE;
centerRect.top = centerHeight - HALF_BOX_SIZE;
centerRect.right = centerWidth + HALF_BOX_SIZE;
centerRect.bottom = centerHeight + HALF_BOX_SIZE;
// Draw the paint with this alpha inside of the center box.
paint.setAlpha(0x55);
canvas.clipRect(centerRect);
canvas.drawPaint(paint);
canvas.restore();
canvas.save();
// Invert the clipped region.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
canvas.clipOutRect(centerRect);
} else {
canvas.clipRect(centerRect, Region.Op.DIFFERENCE);
}
// Draw the paint with this alpha outside of the center box.
paint.setAlpha(0xff);
canvas.drawPaint(paint);
canvas.restore();
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(#Nullable ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
private static final int BOX_SIZE = 150; // width & height in pixels
private static final int HALF_BOX_SIZE = BOX_SIZE / 2;
}
With this drawable, you can set it to the background of the layout like this:
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.background = MyBackgroundDrawable()
If you have a minSdk of API 24 or higher, you can define this drawable in a drawable file as follows:
cutout_with_alpha.xml
<?xml version="1.0" encoding="utf-8"?>
<drawable xmlns:tools="http://schemas.android.com/tools"
class="com.example.backgroundalpha.MyBackgroundDrawable"
tools:targetApi="n" />
You can then set the background in XML like so:
<androidx.constraintlayout.widget.ConstraintLayout
...
android:background="#drawable/cutout_with_alpha">

How to do wrap content on half circle custom view?

I'm making half circle custom view on Android. However, I'm struggling to remove the un-needed bottom white space on wrap content. I think because it is drawing based on 'a full circle' calculation.
I'm sharing my Custom View implementation, as well as how I call it from my application.
See also this image:
Click here for the screenshot
Note: If I change the onMeasure size, then it will cut the upper circle:
Click here for the screenshot
class CircularProgressView(context: Context, attrs: AttributeSet) : View(context, attrs) {
private var circle = RectF()
private val paint = Paint()
private var size = 0
init {
paint.isAntiAlias = true
paint.style = Paint.Style.STROKE
paint.strokeWidth = strokeWidth.toFloat()
paint.strokeCap = Paint.Cap.BUTT
setupAttributes(attrs)
}
private fun setupAttributes(attrs: AttributeSet?) {
// TypedArray objects are shared and must be recycled.
typedArray.recycle()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawBackground(canvas)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
size = Math.min(measuredWidth, measuredHeight)
setMeasuredDimension(size, size)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
val centerX = w / 2
val centerY = h / 2
// Pick the minimum value so that it can fit the container. Radius is half size
val radius = size / 2f
// Create the background and progress circle, adding dialStrokeWidth in equation so that make sure the dial can fit container
circle.top = (centerY - radius)
circle.bottom = (centerY + radius)
circle.left = (centerX - radius)
circle.right = (centerX + radius)
}
private fun drawBackground(canvas: Canvas) {
paint.shader = null
paint.color = backGroundColor
canvas.drawArc(circle, startAngle, sweepAngle, false, paint)
}
}
This is how I use it:
<com.enova.circular_progress.CircularProgressView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#color/white"
app:backgroundColor="#color/colorHint"
app:dialColor="#color/colorPrimary"
app:foregroundColorEnd="#color/colorPrimary"
app:foregroundColorStart="#color/colorPrimaryDark"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:percent="80">
</com.enova.circular_progress.CircularProgressView>
Change your onMeasure. YOu're setting your measured width and height to the same value. If you only want to display a top half circle, then you want to set the height to half the width. Otherwise, it will think the view is the full circle in height.

How to fit content of GIF animation in View and in live wallpaper?

Background
I have a small live wallpaper app, that I want to add support for it to show GIF animations.
For this, I've found various solutions. There is the solution of showing a GIF animation in a view (here), and there is even a solution for showing it in a live wallpaper (here).
However, for both of them, I can't find how to fit the content of the GIF animation nicely in the space it has, meaning any of the following:
center-crop - fits to 100% of the container (the screen in this case), cropping on sides (top&bottom or left&right) when needed. Doesn't stretch anything. This means the content seems fine, but not all of it might be shown.
fit-center - stretch to fit width/height
center-inside - set as original size, centered, and stretch to fit width/height only if too large.
The problem
None of those is actually about ImageView, so I can't just use the scaleType attribute.
What I've found
There is a solution that gives you a GifDrawable (here), which you can use in ImageView, but it seems it's pretty slow in some cases, and I can't figure out how to use it in LiveWallpaper and then fit it.
The main code of the LiveWallpaper GIF handling is as such (here) :
class GIFWallpaperService : WallpaperService() {
override fun onCreateEngine(): WallpaperService.Engine {
val movie = Movie.decodeStream(resources.openRawResource(R.raw.cinemagraphs))
return GIFWallpaperEngine(movie)
}
private inner class GIFWallpaperEngine(private val movie: Movie) : WallpaperService.Engine() {
private val frameDuration = 20
private var holder: SurfaceHolder? = null
private var visible: Boolean = false
private val handler: Handler = Handler()
private val drawGIF = Runnable { draw() }
private fun draw() {
if (visible) {
val canvas = holder!!.lockCanvas()
canvas.save()
movie.draw(canvas, 0f, 0f)
canvas.restore()
holder!!.unlockCanvasAndPost(canvas)
movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
handler.removeCallbacks(drawGIF)
handler.postDelayed(drawGIF, frameDuration.toLong())
}
}
override fun onVisibilityChanged(visible: Boolean) {
this.visible = visible
if (visible)
handler.post(drawGIF)
else
handler.removeCallbacks(drawGIF)
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(drawGIF)
}
override fun onCreate(surfaceHolder: SurfaceHolder) {
super.onCreate(surfaceHolder)
this.holder = surfaceHolder
}
}
}
The main code for handling GIF animation in a view is as such:
class CustomGifView #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
private var gifMovie: Movie? = null
var movieWidth: Int = 0
var movieHeight: Int = 0
var movieDuration: Long = 0
var mMovieStart: Long = 0
init {
isFocusable = true
val gifInputStream = context.resources.openRawResource(R.raw.test)
gifMovie = Movie.decodeStream(gifInputStream)
movieWidth = gifMovie!!.width()
movieHeight = gifMovie!!.height()
movieDuration = gifMovie!!.duration().toLong()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(movieWidth, movieHeight)
}
override fun onDraw(canvas: Canvas) {
val now = android.os.SystemClock.uptimeMillis()
if (mMovieStart == 0L) { // first time
mMovieStart = now
}
if (gifMovie != null) {
var dur = gifMovie!!.duration()
if (dur == 0) {
dur = 1000
}
val relTime = ((now - mMovieStart) % dur).toInt()
gifMovie!!.setTime(relTime)
gifMovie!!.draw(canvas, 0f, 0f)
invalidate()
}
}
}
The questions
Given a GIF animation, how can I scale it in each of the above ways?
Is it possible to have a single solution for both cases?
Is it possible to use GifDrawable library (or any other drawable for the matter) for the live wallpaper, instead of the Movie class? If so, how?
EDIT: after finding how to scale for 2 kinds, I still need to know how to scale according to the third type, and also want to know why it keeps crashing after orientation changes, and why it doesn't always show the preview right away.
I'd also like to know what's the best way to show the GIF animation here, because currently I just refresh the canvas ~60fps (1000/60 waiting between each 2 frames), without consideration of what's in the file.
Project is available here.
If you have Glide in your project, You can easily load Gifs, as it provides drawing GIFs to your ImageViews and does support many scaling options (like center or a given width and ...).
Glide.with(context)
.load(imageUrl or resourceId)
.asGif()
.fitCenter() //or other scaling options as you like
.into(imageView);
OK I think I got how to scale the content. Not sure though why the app still crashes upon orientation change sometimes, and why the app doesn't show the preview right away sometimes.
Project is available here.
For center-inside, the code is:
private fun draw() {
if (!isVisible)
return
val canvas = holder!!.lockCanvas() ?: return
canvas.save()
//center-inside
val scale = Math.min(canvas.width.toFloat() / movie.width().toFloat(), canvas.height.toFloat() / movie.height().toFloat());
val x = (canvas.width.toFloat() / 2f) - (movie.width().toFloat() / 2f) * scale;
val y = (canvas.height.toFloat() / 2f) - (movie.height().toFloat() / 2f) * scale;
canvas.translate(x, y)
canvas.scale(scale, scale)
movie.draw(canvas, 0f, 0f)
canvas.restore()
holder!!.unlockCanvasAndPost(canvas)
movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
handler.removeCallbacks(drawGIF)
handler.postDelayed(drawGIF, frameDuration.toLong())
}
For center-crop, the code is:
private fun draw() {
if (!isVisible)
return
val canvas = holder!!.lockCanvas() ?: return
canvas.save()
//center crop
val scale = Math.max(canvas.width.toFloat() / movie.width().toFloat(), canvas.height.toFloat() / movie.height().toFloat());
val x = (canvas.width.toFloat() / 2f) - (movie.width().toFloat() / 2f) * scale;
val y = (canvas.height.toFloat() / 2f) - (movie.height().toFloat() / 2f) * scale;
canvas.translate(x, y)
canvas.scale(scale, scale)
movie.draw(canvas, 0f, 0f)
canvas.restore()
holder!!.unlockCanvasAndPost(canvas)
movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
handler.removeCallbacks(drawGIF)
handler.postDelayed(drawGIF, frameDuration.toLong())
}
for fit-center, I can use this:
val canvasWidth = canvas.width.toFloat()
val canvasHeight = canvas.height.toFloat()
val bitmapWidth = curBitmap.width.toFloat()
val bitmapHeight = curBitmap.height.toFloat()
val scaleX = canvasWidth / bitmapWidth
val scaleY = canvasHeight / bitmapHeight
scale = if (scaleX * curBitmap.height > canvas.height) scaleY else scaleX
x = (canvasWidth / 2f) - (bitmapWidth / 2f) * scale
y = (canvasHeight / 2f) - (bitmapHeight / 2f) * scale
...
Change the width and the height of the movie:
Add this code in onDraw method before movie.draw
canvas.scale((float)this.getWidth() / (float)movie.width(),(float)this.getHeight() / (float)movie.height());
or
canvas.scale(1.9f, 1.21f); //this changes according to screen size
Scale to fill and scale to fit:
There's already a good answer on that:
https://stackoverflow.com/a/38898699/5675325

Letter avatar like Gmail Android best practice

What is the best way of generating (in code) a letter avatar like in Gmail?
Here you have an example
https://drive.google.com/folderview?id=0B0Fhz5fDg1njSmpUakhhZllEWHM&usp=sharing
It should look like that:
This is what I've used once.. please try and modify according to your requirements.
public class LetterAvatar extends ColorDrawable {
Paint paint = new Paint();
Rect bounds = new Rect();
String pLetters;
private float ONE_DP = 0.0f;
private Resources pResources;
private int pPadding;
int pSize = 0;
float pMesuredTextWidth;
int pBoundsTextwidth;
int pBoundsTextHeight;
public LetterAvatar (Context context, int color, String letter, int paddingInDp) {
super(color);
this.pLetters = letter;
this.pResources = context.getResources();
ONE_DP = 1 * pResources.getDisplayMetrics().density;
this.pPadding = Math.round(paddingInDp * ONE_DP);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
paint.setAntiAlias(true);
do {
paint.setTextSize(++pSize);
paint.getTextBounds(pLetters, 0, pLetters.length(), bounds);
} while ((bounds.height() < (canvas.getHeight() - pPadding)) && (paint.measureText(pLetters) < (canvas.getWidth() - pPadding)));
paint.setTextSize(pSize);
pMesuredTextWidth = paint.measureText(pLetters);
pBoundsTextHeight = bounds.height();
float xOffset = ((canvas.getWidth() - pMesuredTextWidth) / 2);
float yOffset = (int) (pBoundsTextHeight + (canvas.getHeight() - pBoundsTextHeight) / 2);
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
paint.setColor(0xffffffff);
canvas.drawText(pLetters, xOffset, yOffset, paint);
}
}
then set new LetterAvatar(context, colorCode, letters, padding) in your imageview.setdrawable
If you are asking only about avatar on the left in that ListView.
Use ImageView, and if you have user avatar - put it there, if you don't have avatar - use .drawText("R") function to draw canvas and put it in ImageView using setImageDrawable.
Judging from the image you provided above, this can be done using a custom listview. The avatar you are looking for should be an imageview in your custom layout and inflated into the listview. I suggest you start here. The gravar can be an image drawable in your resource folder,
http://www.ezzylearning.com/tutorial.aspx?tid=1763429
Below is a class that generates an Image avatar both a circle and Square. Source is here
class AvatarGenerator {
companion object {
lateinit var uiContext: Context
var texSize = 0F
fun avatarImage(context: Context, size: Int, shape: Int, name: String): BitmapDrawable {
uiContext = context
val width = size
val hieght = size
texSize = calTextSize(size)
val label = firstCharacter(name)
val textPaint = textPainter()
val painter = painter()
val areaRect = Rect(0, 0, width, width)
if (shape == 0) {
painter.color = RandomColors().getColor()
} else {
painter.color = Color.TRANSPARENT
}
val bitmap = Bitmap.createBitmap(width, width, ARGB_8888)
val canvas = Canvas(bitmap)
canvas.drawRect(areaRect, painter)
//reset painter
if (shape == 0) {
painter.color = Color.TRANSPARENT
} else {
painter.color = RandomColors().getColor()
}
val bounds = RectF(areaRect)
bounds.right = textPaint.measureText(label, 0, 1)
bounds.bottom = textPaint.descent() - textPaint.ascent()
bounds.left += (areaRect.width() - bounds.right) / 2.0f
bounds.top += (areaRect.height() - bounds.bottom) / 2.0f
canvas.drawCircle(width.toFloat() / 2, hieght.toFloat() / 2, width.toFloat() / 2, painter)
canvas.drawText(label, bounds.left, bounds.top - textPaint.ascent(), textPaint)
return BitmapDrawable(uiContext.resources, bitmap)
}
private fun firstCharacter(name: String): String {
return name.first().toString().toUpperCase()
}
private fun textPainter(): TextPaint {
val textPaint = TextPaint()
textPaint.textSize = texSize * uiContext.resources.displayMetrics.scaledDensity
textPaint.color = Color.WHITE
return textPaint
}
private fun painter(): Paint {
return Paint()
}
private fun calTextSize(size: Int): Float {
return (size / 3.125).toFloat()
}
}
}
You can then pass context, a string, size, and the shape to generate 1/0
imageView.setImageDrawable(
AvatarGenerator.avatarImage(
this,
200,
1,
"Skyways"
)

Scale images inside textview using ImageGetter

I'm trying to scale the images showed in the textview but i just can't.
I'm using this code but no matter what, it shows the image cropped inside the container or doesn't show at all.
int width, height;
DisplayMetrics metrics = new DisplayMetrics();
metrics = Resources.getSystem().getDisplayMetrics();
int originalWidthScaled = (int) (result.getIntrinsicWidth() * metrics.density);
int originalHeightScaled = (int) (result.getIntrinsicHeight() * metrics.density);
if (originalWidthScaled > metrics.widthPixels) {
height = result.getIntrinsicHeight() * metrics.widthPixels
/ result.getIntrinsicWidth();
width = metrics.widthPixels;
} else {
height = originalHeightScaled;
width = originalWidthScaled;
}
urlDrawable.drawable = result;
urlDrawable.setBounds(0, 0, 0+width, 0+height);
// change the reference of the current drawable to the result
// from the HTTP call
// redraw the image by invalidating the container
container.invalidate();
// For ICS
container.setHeight(
container.getHeight() +
result.getIntrinsicHeight());
// Pre ICS
container.setEllipsize(null);
I answer myself i've changed
if (originalWidthScaled > metrics.widthPixels) {
height = result.getIntrinsicHeight() * metrics.widthPixels
/ result.getIntrinsicWidth();
width = metrics.widthPixels;
}
for
if (originalWidthScaled > (metrics.widthPixels * 70) / 100) {
width = (metrics.widthPixels * 70) / 100;
height = result.getIntrinsicHeight() * width
/ result.getIntrinsicWidth();
}
And now it occupies the 70% of the space of the screen which is exactly the max size of the container
For anyone who still looking for an answer using new APIs, this custom implementation of ImageGetter should allow you to scale up the image which will occupy the device display width, scale down if the given image is larger than the device display width or retain its original dimension if smaller.
/**
* Custom ImageGetter for [HtmlCompat.fromHtml] which accept both Url and Base64 from img tag.
* */
class HtmlImageGetter(
private val scope: LifecycleCoroutineScope,
private val res: Resources,
private val glide: RequestManager,
private val htmlTextView: AppCompatTextView,
#DrawableRes
private val errorImage: Int = 0,
private val matchParent: Boolean = true
) : ImageGetter {
override fun getDrawable(source: String): Drawable {
val holder = BitmapDrawablePlaceHolder(res, null)
scope.launch(Dispatchers.IO) {
runCatching {
glide
.asBitmap()
.load(
if (source.matches(Regex("data:image.*base64.*")))
Base64.decode(
source.replace("data:image.*base64".toRegex(), ""),
Base64.DEFAULT
) // Image tag used Base64
else
source // Image tag used URL
)
.submit()
.get()
}
.onSuccess { setDrawable(holder, it) }
.onFailure {
if (errorImage != 0)
BitmapFactory.decodeResource(res, errorImage)?.let {
setDrawable(holder, it)
}
}
}
return holder
}
private suspend fun setDrawable(holder: BitmapDrawablePlaceHolder, bitmap: Bitmap) {
val drawable = BitmapDrawable(res, bitmap)
val width: Int
val height: Int
val metrics = res.displayMetrics
val displayWidth = metrics.widthPixels - (htmlTextView.paddingStart + htmlTextView.paddingEnd + htmlTextView.marginStart + htmlTextView.marginEnd) * 100 / 100
val imageWidthScaled = (drawable.intrinsicWidth * metrics.density)
val imageHeightScaled = (drawable.intrinsicHeight * metrics.density)
// Scale up if matchParent is true
// Scale down if matchParent is false
if (matchParent || imageWidthScaled > displayWidth) {
width = displayWidth
height = (drawable.intrinsicHeight * width / drawable.intrinsicWidth)
}
else {
height = imageHeightScaled.roundToInt()
width = imageWidthScaled.roundToInt()
}
drawable.setBounds(0, 0, width, height)
holder.setDrawable(drawable)
holder.setBounds(0, 0, width, height)
withContext(Dispatchers.Main) { htmlTextView.text = htmlTextView.text }
}
internal class BitmapDrawablePlaceHolder(res: Resources, bitmap: Bitmap?) :
BitmapDrawable(res, bitmap) {
private var drawable: Drawable? = null
override fun draw(canvas: Canvas) {
drawable?.run { draw(canvas) }
}
fun setDrawable(drawable: Drawable) {
this.drawable = drawable
}
}
}

Categories

Resources