ExoPlayer resize works only first time its called - android

As mentioned above I'm having a problem with changing the resize mode on my ExoPlayer.
I'm playing a list of videos that change on scroll and depending on the screen orientation I'm trying to make the video zoom in if its close to the screen AspectRatio, the block of code that handles that is:
override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
super.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio)
val displayMetrics = DisplayMetrics()
(context as Activity).windowManager.defaultDisplay.getMetrics(displayMetrics)
val displayAspectRatio = displayMetrics.widthPixels.toFloat() / displayMetrics.heightPixels
val videoAspectRatio = width.toFloat() / height
if (abs(videoAspectRatio - displayAspectRatio) < 0.2) {
exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
selectedPlayer?.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
} else {
exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
selectedPlayer?.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
}
}
But the exoPlayerView.resizeMode is visible only the first time this is called.
Is there some other way to do this? Or maybe force the view to re-render?
Thanks in advance!

You can access size of your video with : exoplayer.videoFormat.width and exoplayer.videoFormat.height, so that whenever your video is starting you start your function to calculate the ratio and do your stuff

Related

How to get Width of Button in Android Kotlin

I want to resize the width of My button from Kotlin code.
I tried this
private var viewPagerPageChangeListener: ViewPager.OnPageChangeListener = object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) {
addBottomDots(position)
if (position == layouts!!.size - 1) {
btnNext!!.text = getString(R.string.start)
btnNext!!.layoutParams = RelativeLayout.LayoutParams(315, 44)
btnSkip!!.visibility = View.GONE
} else {
btnNext!!.text = getString(R.string.next)
btnSkip!!.visibility = View.VISIBLE
}
}
I am using this in the View Pager.
After running my application, button is disappeared from the screen.
You can use View's scaleX and scaleY if you just want to simply stretch the view. Getting the actual width and height is a bit complicated as you have to take into account listening to a ViewTreeObserver

Android TextView inside ListView does not measure the correct height until manually scrolling

I have a listView filled with multi-line TextViews. Each TextView has a different amount of text. After pressing a button, the user is taken to another Activity where they can change the font and the font size. Upon reEntry into the Fragment, if these settings have changed, the listView is reset and the measurements of the TextViews are changed.
I need to know the measured height of the first TextView in view after these settings have changed. For some reason, the measured height is different at first after it is measured. Once I manually scroll the list, the real height measurement is recorded.
Log output:
After measured: tv height = 2036
After measured: tv height = 2036
After scroll: tv height = 7950
Minimal Code:
class FragmentRead : Fragment() {
private var firstVisiblePos = 0
lateinit var adapterRead: AdapterRead
lateinit var lvTextList: ListView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
lvTextList = view.findViewById(R.id.read_listview)
setListView(lvTextList)
lvTextList.setOnScrollListener(object : AbsListView.OnScrollListener {
var offset = 0
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
offset = if(lvTextList.getChildAt(0) == null) 0 else lvTextList.getChildAt(0).top - lvTextList.paddingTop
println("After scroll: tv height = ${lvTextList[0].height}")
}
}
override fun onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
firstVisiblePos = firstVisibleItem
}
})
}
/*=======================================================================================================*/
fun setListView(lv: ListView) {
adapterRead = AdapterRead(Data.getTextList(), context!!)
lv.apply {this.adapter = adapterRead}
}
/*=======================================================================================================*/
inline fun <T : View> T.afterMeasured(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if(measuredWidth > 0 && measuredHeight > 0) {
println("After measured: tv height = ${lvTextList[0].height}")
viewTreeObserver.removeOnGlobalLayoutListener(this)
f()
}
}
})
}
/*=======================================================================================================*/
override fun onStart() {
if(Settings.settingsChanged) {
setListView(lvTextList)
lvTextList.afterMeasured {
println("After measured: tv height = ${lvTextList[0].height}")
}
}
}
}
What I have tried:
I have tried setting a TextView with the text and layoutParams and reading the height as explained here (Getting height of text view before rendering to layout) but the results are the same. The measured height is much less than after I scroll the list.
I have also tried to programatically scroll the list using lvTextList.scrollBy(0,1) in order to trigger the scroll listener or whatever else is triggered when the correct height is read.
EDIT: I put a delay in after coming back to the Fragment:
Handler().postDelayed({
println("tv height after delay = ${lvScriptureList[0].height}")}, 1000)
And this reports the correct height. So my guess is that the OnGlobalLayoutListener is being called to early. Any way to fix this?
Here is my solution. The reason I need to know the height of the TextView is because after the user changes settings (e.g. font, font size, line spacing) the size of the TextView changes. In order to return to the same spot the TextView was in previously, I need to know the height of the newly measured TextView. Then I can go to the same spot (or very close) based on the position previously and recalculating it based on the new height.
So after the settings are changed and the Fragment is loaded back up:
override fun onStart(){
if(Settings.settingsChanged) {
setListView(lvTextList)
lvTextList.afterMeasured {
lvTextList.post { lvTextList.setSelectionFromTop(readPos, 0) }
Handler().postDelayed({
val newOffset = getNewOffset() // Recalculates the new offset based on the last offset and the new TextView height
lvTextList.post { lvTextList.setSelectionFromTop(readPos, newOffset) }
}, 500)
}
}
}
For some reason I had to scroll to a position first before scheduling the delay so I simply just scrolled to the beginning of the TextView.
The 500ms is goofy and is just an estimate but it works. It actually works with a value of 100ms on my phone but I want to ensure a better chance of success across devices.

Unable to get bar-code bounding box in right position on overlay Surfaceview

I'm using my CameraX with Firebase MLKit bar-code reader to detect barcode code. Application Identifies the bar-code without a problem. But I'm trying to add bounding box which shows the area of the barcode in CameraX preview in real-time. The Bounding box information is retrieved from the bar-code detector function. But It doesn't have nither right position nor size as you can see below.
This is my layout of the activity.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="#+id/camera_capture_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="50dp"
android:scaleType="fitCenter"
android:text="Take Photo"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:elevation="2dp" />
<SurfaceView
android:id="#+id/overlayView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.camera.view.PreviewView
android:id="#+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
SurfaceView is used to draw this rectangle shape.
Barcode detection happens in the BarcodeAnalyzer class which implements ImageAnalysis.Analyzer. inside overwritten analyze function I retrieve the barcode data like below.
#SuppressLint("UnsafeExperimentalUsageError")
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
val rotationDegrees = degreesToFirebaseRotation(imageProxy.imageInfo.rotationDegrees)
if (mediaImage != null) {
val analyzedImageHeight = mediaImage.height
val analyzedImageWidth = mediaImage.width
val image = FirebaseVisionImage
.fromMediaImage(mediaImage,rotationDegrees)
detector.detectInImage(image)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
val bounds = barcode.boundingBox
val corners = barcode.cornerPoints
val rawValue = barcode.rawValue
if(::barcodeDetectListener.isInitialized && rawValue != null && bounds != null){
barcodeDetectListener.onBarcodeDetect(
rawValue,
bounds,
analyzedImageWidth,
analyzedImageHeight
)
}
}
imageProxy.close()
}
.addOnFailureListener {
Log.e(tag,"Barcode Reading Exception: ${it.localizedMessage}")
imageProxy.close()
}
.addOnCanceledListener {
Log.e(tag,"Barcode Reading Canceled")
imageProxy.close()
}
}
}
barcodeDetectListener is a reference to an interface I create to communicate this data back into my activity.
interface BarcodeDetectListener {
fun onBarcodeDetect(code: String, codeBound: Rect, imageWidth: Int, imageHeight: Int)
}
In my main activity, I send these data to OverlaySurfaceHolder which implements the SurfaceHolder.Callback. This class is responsible for drawing a bounding box on overlayed SurfaceView.
override fun onBarcodeDetect(code: String, codeBound: Rect, analyzedImageWidth: Int,
analyzedImageHeight: Int) {
Log.i(TAG,"barcode : $code")
overlaySurfaceHolder.repositionBound(codeBound,previewView.width,previewView.height,
analyzedImageWidth,analyzedImageHeight)
overlayView.invalidate()
}
As you can see here I'm sending overlayed SurfaceView width and height for the calculation in OverlaySurfaceHolder class.
OverlaySurfaceHolder.kt
class OverlaySurfaceHolder: SurfaceHolder.Callback {
var previewViewWidth: Int = 0
var previewViewHeight: Int = 0
var analyzedImageWidth: Int = 0
var analyzedImageHeight: Int = 0
private lateinit var drawingThread: DrawingThread
private lateinit var barcodeBound :Rect
private val tag = OverlaySurfaceHolder::class.java.simpleName
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
var retry = true
drawingThread.running = false
while (retry){
try {
drawingThread.join()
retry = false
} catch (e: InterruptedException) {
}
}
}
override fun surfaceCreated(holder: SurfaceHolder?) {
drawingThread = DrawingThread(holder)
drawingThread.running = true
drawingThread.start()
}
fun repositionBound(codeBound: Rect, previewViewWidth: Int, previewViewHeight: Int,
analyzedImageWidth: Int, analyzedImageHeight: Int){
this.barcodeBound = codeBound
this.previewViewWidth = previewViewWidth
this.previewViewHeight = previewViewHeight
this.analyzedImageWidth = analyzedImageWidth
this.analyzedImageHeight = analyzedImageHeight
}
inner class DrawingThread(private val holder: SurfaceHolder?): Thread() {
var running = false
private fun adjustXCoordinates(valueX: Int): Float{
return if(previewViewWidth != 0){
(valueX / analyzedImageWidth.toFloat()) * previewViewWidth.toFloat()
}else{
valueX.toFloat()
}
}
private fun adjustYCoordinates(valueY: Int): Float{
return if(previewViewHeight != 0){
(valueY / analyzedImageHeight.toFloat()) * previewViewHeight.toFloat()
}else{
valueY.toFloat()
}
}
override fun run() {
while(running){
if(::barcodeBound.isInitialized){
val canvas = holder!!.lockCanvas()
if (canvas != null) {
synchronized(holder) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
val myPaint = Paint()
myPaint.color = Color.rgb(20, 100, 50)
myPaint.strokeWidth = 6f
myPaint.style = Paint.Style.STROKE
val refinedRect = RectF()
refinedRect.left = adjustXCoordinates(barcodeBound.left)
refinedRect.right = adjustXCoordinates(barcodeBound.right)
refinedRect.top = adjustYCoordinates(barcodeBound.top)
refinedRect.bottom = adjustYCoordinates(barcodeBound.bottom)
canvas.drawRect(refinedRect,myPaint)
}
holder.unlockCanvasAndPost(canvas)
}else{
Log.e(tag, "Cannot draw onto the canvas as it's null")
}
try {
sleep(30)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}
}
}
Please can anyone point me out what am I doing wrong?
I don't have a very clear clue, but here are something you could try:
When you adjustXCoordinates, if previewWidth is 0, you return valueX.toFloat() directly. Could you add something logging to see it it actually falls into this case? Also adding some logs to print the analysis and preview dimension could be helpful as well.
Another thing worth noting is that the image you sent to the detector could have different aspect ratio from the preview View area. For example, if your camera takes a 4:3 photo, it will send it to detector. However, if your View area is 1:1, it will crop some part of the photos to display it there. In that case, you need to take this into consideration as well when adjust coordinates. Base on my testing, the image will fit into the View area based on CENTER_CROP. If you want to be really careful, probably worth checking if this is documented in the camera dev site.
Hope it helps, more or less.
I am no longer working on this project. However resonantly I worked on a camera application that uses Camera 2 API. In that application, there was a requirement to detect the object using the MLKit object detection library and show the bounding box like this on top of the camera preview. Faced the same issue like this one first and manage to get it to work finally. I'll leave my approach here. It might help someone.
Any detection library will do its detection process in a small resolution image compare to the camera preview image. When the detection library returns the combinations for the detected object we need to scale up to show it in the right position. it's called the scale factor. In order to make the calculation easy, it's better to select the analyze image size and preview image size in the same aspect ratio.
You can use the below function to get the aspect ratio of any size.
fun gcd(a: Long, b: Long): Long {
return if (b == 0L) a else gcd(b, a % b)
}
fun asFraction(a: Long, b: Long): Pair<Long,Long> {
val gcd = gcd(a, b)
return Pair((a / gcd) , b / gcd)
}
After getting the camera preview image aspect ratio, selected the analyze image size like below.
val previewFraction = DisplayUtils
.asFraction(previewSize!!.width.toLong(),previewSize!!.height.toLong())
val analyzeImageSize = characteristics
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
.getOutputSizes(ImageFormat.YUV_420_888)
.filter { DisplayUtils.asFraction(it.width.toLong(), it.height.toLong()) == previewFraction }
.sortedBy { it.height * it.width}
.first()
Finaly when you have these two values you can calculate scale factor like below.
val scaleFactor = previewSize.width / analyzedSize.width.toFloat()
Finaly before the bounding box is drawn to the multiply each opint with scale factor to get correct screen coordinations.
if detect from bitmap, your reposition method will be right as i try.

Espresso with Custom KeyboardView button press

I am implementing a custom KeyboardView in my app and it's all working at the moment, however, when I attempt to press a key on the keyboard using Espresso ViewAction, I am getting an exception saying:
android.support.test.espresso.PerformException:
Error performing 'single click - At Coordinates: 1070, 2809 and
precision: 16, 16' on view 'with id:
com.example.app.mvpdemo:id/keyboardLayout'.
The code throwing the exception is:
#Test
fun enter100AsPriceShouldDisplay120ForA20PercentTip(){
onView(withId(R.id.editTextCheckAmount))
.perform(typeText("100"), closeSoftKeyboard())
val appContext = InstrumentationRegistry.getTargetContext()
val displayMetrics = appContext.resources.displayMetrics
onView(withId(R.id.keyboardLayout)).perform(clickXY(displayMetrics.widthPixels - 10, displayMetrics.heightPixels - 10))
onView(withText("$120.00")).check(matches(isDisplayed()))
}
and the click XY function which came from this post
private fun clickXY(x: Int, y: Int): ViewAction {
return GeneralClickAction(
Tap.SINGLE,
CoordinatesProvider { view ->
val screenPos = IntArray(2)
view.getLocationOnScreen(screenPos)
val screenX = (screenPos[0] + x).toFloat()
val screenY = (screenPos[1] + y).toFloat()
floatArrayOf(screenX, screenY)
},
Press.FINGER, 0, 0)
}
Here is my keyboard layout (pinned to the bottom of the screen inside a ConstraintLayout):
Does anyone know why? Any help is appreciated.
Answering my own question after determining a flexible solution:
First attempt - get DisplayMetrics of the root View and subtract an arbitrary number to attempt to hit the Keyboard.Key
this didn't work because clickXY function uses the position of the view
this ended up being the reason for the exception since the view is smaller than the DisplayMetrics values and adding to the Views on screen position would give a very high number for the x and y.
So I tried again,
Second attempt - use check method on the ViewMatcher to check the KeyBoardView.
by doing so I was able to get access to the KeyboardView's position x
then I was able to get the KeyboardView's width and height
by performing some math, I was able to figure out target index for x & y
the math:
take the widthPercent for the Keyboard.Key (in my case 33.3%)
take the rowCount of the keyboard.xml (in my case 3)
use (viewWidth * widthPercent) / 4 to get relativeButtonX
use (viewHeight / rowCount) / 2 to get relativeButtonY
then for targetY, I took viewHeight - relativeButtonY
finally, for targetX, I took (viewPosX + viewWidth) - relativeButtonX
So enough explanation, here is the code:
#Test
fun enter100AsPriceShouldDisplay120ForA20PercentTip() {
onView(withId(R.id.editTextCheckAmount))
.perform(typeText("100"), closeSoftKeyboard())
// call the function to get the targets
val (viewTargetY, viewTargetX) = getTargetXAndY()
// perform the action
onView(withId(R.id.keyboardLayout)).perform(clickXY(viewTargetX.toInt(), viewTargetY))
onView(withText("Tip: $20.00")).check(matches(isDisplayed()))
onView(withText("Total: $120.00")).check(matches(isDisplayed()))
}
and the helper method with all the math:
private fun getTargetXAndY(): Pair<Int, Double> {
var viewHeight = 0
var viewWidth = 0
var viewPosX = 0F
val viewMatcher = onView(withId(R.id.keyboardLayout))
viewMatcher.check { view, _ ->
viewWidth = view.width
viewHeight = view.height
viewPosX = view.x
}
val keyboardKeyWidthPercent = 0.333
val keyboardRowsCount = 3
val keyboardButtonWidthQuarter = (viewWidth * keyboardKeyWidthPercent) / 4
val keyboardButtonHeightHalf = (viewHeight / keyboardRowsCount) / 2
val viewTargetY = viewHeight - keyboardButtonHeightHalf
val viewTargetX = (viewPosX + viewWidth) - keyboardButtonWidthQuarter
return Pair(viewTargetY, viewTargetX)
}
Now, the click is not perfectly centered but it clicks the button pretty close to the center.

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

Categories

Resources