I want to use AccessibilityService to do a screencapture when a specific app is in use.
Running the AccessibilityServiceand using windowManager.addView to display the overlaid button on the screen was successful.
But how do i take a screenshot of a specific app?
class MyAccessibilityService : AccessibilityService() {
var layout: LinearLayout? = null
var flayout: View? = null
var initialX = 0f
var initialY = 0f
var initialTouchX = 0f
var initialTouchY = 0f
var lastAction: Int? = null
val flayoutParams = LayoutParams()
var componentName: ComponentName? = null;
var lastActivity: ActivityInfo? = null;
fun screenShot(view: View): Bitmap? {
val bitmap = Bitmap.createBitmap(
view.width,
view.height, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
view.draw(canvas)
return bitmap
}
#RequiresApi(Build.VERSION_CODES.M)
#SuppressLint("ClickableViewAccessibility")
override fun onServiceConnected() {
val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
if (flayout == null) {
flayout = LayoutInflater.from(this).inflate(R.layout.button, null);
flayoutParams.apply {
y = 0
x = 0
width = LayoutParams.WRAP_CONTENT
height = LayoutParams.WRAP_CONTENT
type = LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
gravity = Gravity.TOP or Gravity.LEFT
format = PixelFormat.TRANSPARENT
flags = LayoutParams.FLAG_NOT_FOCUSABLE
}
try {
windowManager.addView(flayout, flayoutParams)
} catch (ex: Exception) {
Log.e("ACCSVC", "adding view failed", ex)
}
}
flayout!!.setOnTouchListener { v, event ->
when(event.action) {
MotionEvent.ACTION_DOWN -> {
//remember the initial position.
initialX = flayoutParams.x.toFloat();
initialY = flayoutParams.y.toFloat();
//get the touch location
initialTouchX = event.rawX;
initialTouchY = event.rawY;
lastAction = event.action;
true
}
MotionEvent.ACTION_MOVE -> {
//Calculate the X and Y coordinates of the view.
flayoutParams.x = (initialX + (event.rawX - initialTouchX).toInt()).toInt()
flayoutParams.y = (initialY + (event.rawY - initialTouchY).toInt()).toInt()
//Update the layout with new X & Y coordinate
windowManager.updateViewLayout(flayout, flayoutParams)
lastAction = event.action
true
}
MotionEvent.ACTION_UP -> {
//take screen shot
}
}
true
}
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
if (event != null) {
if (event.packageName == "com.tests.myapp") {
componentName = ComponentName(
event.packageName.toString(),
event.className.toString()
)
val activity = tryGetActivity(componentName!!);
if (activity != null){
lastActivity = activity;
}
}else if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
componentName = null
lastActivity = null
}
}
}
private fun tryGetActivity(componentName: ComponentName): ActivityInfo? {
return try {
packageManager.getActivityInfo(componentName, 0)
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
override fun onInterrupt() {
Log.i("ACCSVC", "interrupt")
}
object ScreenMetricsCompat {
private val api: Api =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ApiLevel30()
else Api()
/**
* Returns screen size in pixels.
*/
#RequiresApi(Build.VERSION_CODES.M)
fun getScreenSize(context: Context): Size = api.getScreenSize(context)
#Suppress("DEPRECATION")
private open class Api {
#RequiresApi(Build.VERSION_CODES.M)
open fun getScreenSize(context: Context): Size {
val display = context.getSystemService(WindowManager::class.java).defaultDisplay
val metrics = if (display != null) {
DisplayMetrics().also { display.getRealMetrics(it) }
} else {
Resources.getSystem().displayMetrics
}
return Size(metrics.widthPixels, metrics.heightPixels)
}
}
#RequiresApi(Build.VERSION_CODES.R)
private class ApiLevel30 : Api() {
override fun getScreenSize(context: Context): Size {
val metrics: WindowMetrics = context.getSystemService(WindowManager::class.java).currentWindowMetrics
return Size(metrics.bounds.width(), metrics.bounds.height())
}
}
}
}
I want to put the ability to take screenshots in MotionEvent.ACTION_UP.
I use onAccessibilityEventto check if the app I want is running and save ActivityInfo.
But I don't know how to take a screenshot using ActivityInfo.
I tried using rootInActiveWindow.windowinstead of ActivityInfo, but also couldn't figure out how to screen capture AccessibilityWindowInfo.
I want a screenshot of the app, not a screenshot of the screen. Other answers I've seen said to use getWindow().getDecorView().getRootView().
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed yesterday.
Improve this question
The app I'm going to write is an obstacle avoidance game.
Enemies and obstacles appear, and enemies move freely, and obstacles are created with random coordinates at a specific time. The game ends when the character hits an enemy or an obstacle. Set a record by raising 1 point per second.
It's my first time developing, so I have a lot of problems with the code.There are many problems with the corresponding kotlin code when writing the obstacle avoidance game. Please solve the problem.
Obstacles or enemies are not visible or are in a fixed position when launching the app. We want to keep our enemies free, and obstacles are created at a particular time and in a particular place.
I don't think the crash was handled.
I want to raise my score by 1 point per second.
I want to move the character to the desired point through a touch event.
The default activity file contains the following:
package com.example.myapplication
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Rect
import android.media.MediaPlayer
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.view.MotionEvent
import android.view.View
import android.widget.Button
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import java.util.*
import kotlin.math.roundToInt
class MainActivity : AppCompatActivity() {
private lateinit var startButton: Button
private lateinit var character: View
private lateinit var scoreTextView: TextView
private lateinit var gameOverTextView: TextView
private lateinit var obstacleList: MutableList<View>
private lateinit var enemyList: List<View>
private lateinit var backgroundMusic: MediaPlayer
private lateinit var collisionSound: MediaPlayer
private lateinit var gameOverText: String
private lateinit var res: Resources
private val handlerThread = HandlerThread("MyHandlerThread")
private var timer = Timer(true)
private val enemyRect = Rect()
private val obstacleRect = Rect()
private lateinit var obstacleMovementTask: ObstacleMovementTask
private lateinit var moveEnemyTask: MoveEnemyTask
private lateinit var gameoverDetectionTask: GameoverDetectionTask
private lateinit var handler: Handler
private var gameover = false
private var score = 0
private var obstacleSpeed = 10
private var enemySpeed = 20
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
val screenHeight = Resources.getSystem().displayMetrics.heightPixels
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startButton = findViewById(R.id.start_button)
startButton.setOnClickListener {
startGame()
}
character = findViewById(R.id.character)
character.setOnTouchListener { _, event -> handleTouch(event) }
scoreTextView = findViewById(R.id.score_textview)
gameOverTextView = findViewById(R.id.game_over_textview)
obstacleList = mutableListOf()
enemyList = getEnemyList()
backgroundMusic = MediaPlayer.create(this, R.raw.background_music)
collisionSound = MediaPlayer.create(this, R.raw.collision_sound)
res = resources
gameOverText = res.getString(R.string.game_over_text)
handlerThread.start()
handler = Handler(handlerThread.looper)
obstacleMovementTask = ObstacleMovementTask()
moveEnemyTask = MoveEnemyTask()
gameoverDetectionTask = GameoverDetectionTask()
}
private fun handleTouch(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
character.x = event.x - character.width / 2f
character.y = event.y - character.height / 2f
}
}
return true
}
private fun View.jump() {
val jumpHeight = 400
val jumpDuration = 800L
val jumpHandler = Handler()
var jumpStartTime: Long = 0
var jumpStartY = 0f
jumpHandler.post(object : Runnable {
override fun run() {
if (jumpStartTime == 0L) {
jumpStartTime = System.currentTimeMillis()
jumpStartY = translationY
}
val elapsedTime = System.currentTimeMillis() - jumpStartTime
val t = elapsedTime.toFloat() / jumpDuration
val y = jumpHeight * 4 * (t - 0.5f) * (t - 0.5f)
translationY = jumpStartY - y
if (t < 1.0f) {
jumpHandler.postDelayed(this, 16)
} else {
jumpHandler.removeCallbacks(this)
}
}
})
}
private fun View.cancelAnimation() {
clearAnimation()
}
private val outRect = Rect()
private fun startGame() {
gameover = false
score = 0
scoreTextView.text = "Score: 0"
gameOverTextView.visibility = View.INVISIBLE
character.translationY = 0f
obstacleList.takeIf { it.size > 0 }?.forEachIndexed { index, obstacle ->
obstacle.translationX = screenWidth.toFloat()
enemyList[index].translationX = screenWidth.toFloat()
}
startButton.visibility = View.GONE
backgroundMusic.isLooping = true
backgroundMusic.start()
character.x = screenWidth / 4f
character.y = screenHeight / 2f
obstacleList = mutableListOf()
val obstacleColors = listOf(
Color.parseColor("#FF4081"),
Color.parseColor("#2196F3"),
Color.parseColor("#4CAF50"),
Color.parseColor("#9C27B0"),
Color.parseColor("#FF5722")
)
findViewById<RelativeLayout>(R.id.obstacles_container).apply {
for (i in 0..4) {
val obstacle = layoutInflater.inflate(R.layout.obstacle_layout, null)
obstacle.setBackgroundColor(obstacleColors[i])
addView(obstacle)
obstacleList.add(obstacle)
}
}
val obstacleMovementRunnable = object : Runnable {
override fun run() {
obstacleList.forEach {
it.translationX -= obstacleSpeed
it.getHitRect(obstacleRect)
character.getHitRect(outRect)
if (obstacleRect.intersect(outRect)) {
gameover = true
}
}
if (gameover) {
gameOver()
}
handler.postDelayed(this, 20)
}
}
val moveEnemyRunnable = object : Runnable {
override fun run() {
enemyList.forEach {
it.translationX -= enemySpeed
it.getHitRect(enemyRect)
character.getHitRect(outRect)
if (enemyRect.intersect(outRect)) {
gameover = true
}
}
}
}
val gameoverDetectionRunnable = object : Runnable {
override fun run() {
if (gameover) {
handler.removeCallbacks(obstacleMovementRunnable)
handler.removeCallbacks(this)
}
score += 1
scoreTextView.text = "Score: $score"
handler.postDelayed(this, 10)
}
}
handler.post(obstacleMovementRunnable)
handler.postDelayed(moveEnemyRunnable, 0)
handler.postDelayed(gameoverDetectionRunnable, 0)
}
private fun updateScore() {
scoreTextView.text = "Score: $score"
}
private fun stopGame() {
gameover = true
timer.cancel()
backgroundMusic.stop()
collisionSound.start()
gameOverTextView.text = "$gameOverText\nScore: $score"
gameOverTextView.visibility = View.VISIBLE
}
private fun getObstacleList(): List<View> {
val obstacleList = mutableListOf<View>()
val obstacleColors = mutableListOf<Int>()
obstacleColors.add(Color.parseColor("#FF4081"))
obstacleColors.add(Color.parseColor("#2196F3"))
obstacleColors.add(Color.parseColor("#4CAF50"))
obstacleColors.add(Color.parseColor("#9C27B0"))
obstacleColors.add(Color.parseColor("#FF5722"))
for (i in 0..4) {
val obstacle = layoutInflater.inflate(R.layout.obstacle_layout, null)
obstacle.setBackgroundColor(obstacleColors[i])
(findViewById<RelativeLayout>(R.id.obstacles_container)).addView(obstacle)
obstacleList.add(obstacle)
}
return obstacleList
}
private fun getEnemyList(): List<View> {
val enemyList = mutableListOf<View>()
for (i in 0..4) {
val enemy = layoutInflater.inflate(R.layout.enemy_layout, null)
val enemyView = enemy.findViewById<View>(R.id.enemy)
enemyList.add(enemyView)
}
return enemyList
}
private inner class ObstacleMovementTask : TimerTask() {
override fun run() {
if (!gameover) {
obstacleList.forEach { obstacle ->
runOnUiThread {
val screenHeight = Resources.getSystem().displayMetrics.heightPixels
val translationY = (0..(screenHeight - obstacle.height)).random().toFloat()
obstacle.translationY = translationY
}
}
} else {
obstacleList.forEach { it.cancelAnimation() }
}
}
}
private val characterHitBox: Rect by lazy {
val left = character.left
val top = character.top
val right = character.right
val bottom = character.bottom
Rect(left, top, right, bottom)
}
private inner class MoveEnemyTask : TimerTask() {
override fun run() {
runOnUiThread {
for (enemy in enemyList) {
enemy.getHitRect(enemyRect)
enemyRect.left -= enemySpeed
enemyRect.right -= enemySpeed
enemy.translationX -= enemySpeed
if (enemy.translationX < -enemy.width) {
enemy.translationX = screenWidth.toFloat()
enemy.translationY = (0..(screenHeight - enemy.height)).random().toFloat()
}
}
}
}
}
private inner class GameoverDetectionTask : TimerTask() {
override fun run() {
if (gameover) {
handler.post {
stopGame()
}
}
val characterRect = Rect(
character.left + character.width / 3,
character.top + character.height / 3,
character.right - character.width / 3,
character.bottom - character.height / 3
)
for (i in 0..4) {
enemyList[i].getHitRect(enemyRect)
if (characterRect.intersect(enemyRect)) {
runOnUiThread {
stopGame()
}
}
obstacleList[i].getHitRect(obstacleRect)
if (characterRect.intersect(obstacleRect)) {
runOnUiThread {
stopGame()
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
handlerThread.quitSafely()
timer.cancel()
backgroundMusic.release()
collisionSound.release()
}
companion object {
private var screenWidth = 0
private var screenHeight = 0
}
}
I tried various functions such as collision processing, object movement, and score increase.
This is my code for class file
class LiveVideoFragment : BaseFragment(), IVLCVout.Callback {
private var libvlc: LibVLC? = null
private lateinit var holder: SurfaceHolder
private lateinit var mMediaPlayer: MediaPlayer
private var mFilePath = "rtsp://192.168.0.1:554/livestream/1"
private var mVideoWidth = 0
private var mVideoHeight = 0
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_rove_r3_live_video, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
showLiveVideoWhenR3Connected()
}
override fun onPause() {
super.onPause()
Log.d("CALLBACKK", "onPause")
Handler(Looper.getMainLooper()).postDelayed({ releasePlayer() }, 200)
}
private fun showLiveVideoWhenR3Connected() {
handleRtsp()
createPlayer(mFilePath)
}
override fun onNewLayout(
vlcVout: IVLCVout?,
width: Int,
height: Int,
visibleWidth: Int,
visibleHeight: Int,
sarNum: Int,
sarDen: Int
) {
if (width * height == 0) return
mVideoWidth = width
mVideoHeight = height
setSize(mVideoWidth, mVideoHeight)
}
override fun onSurfacesCreated(vlcVout: IVLCVout?) {
}
override fun onSurfacesDestroyed(vlcVout: IVLCVout?) {
}
override fun onHardwareAccelerationError(vlcVout: IVLCVout?) {
Handler(Looper.getMainLooper()).postDelayed({ releasePlayer() }, 200)
Toast.makeText(
requireContext(),
R.string.error_with_hardware_acceleration,
Toast.LENGTH_LONG
)
.show()
}
fun createPlayer(path: String) {
try {
releasePlayer()
// TODO: make this more robust, and sync with audio demo
val options = ArrayList<String>()
options.add("--audio-time-stretch") // time stretching
options.add("-vvv") // verbosity
options.add("--rtsp-tcp")
libvlc = LibVLC(context, options)
holder.setKeepScreenOn(true)
// Creating media player
mMediaPlayer = MediaPlayer(libvlc)
mMediaPlayer.setEventListener(mPlayerListener)
// Seting up video output
val vout: IVLCVout = mMediaPlayer.vlcVout
vout.setVideoView(view_surface)
vout.addCallback(this)
vout.attachViews()
val m = Media(libvlc, Uri.parse(path))
val cache = 500
m.addOption(":network-caching=$cache")
m.addOption(":file-caching=$cache")
m.addOption(":live-cacheing=$cache")
m.addOption(":sout-mux-caching=$cache")
m.addOption(":codec=mediacodec,iomx,all")
mMediaPlayer.media = m
mMediaPlayer.play()
val volume: Int = mMediaPlayer.volume
mMediaPlayer.volume = 0
Log.i("TAG", "createPlayerVolume: $volume")
} catch (e: Exception) {
Log.i("TAG", "createPlayer: " + e.localizedMessage)
}
}
private fun releasePlayer() {
if (libvlc == null)
return
mMediaPlayer.stop()
val vout = mMediaPlayer.vlcVout
vout.removeCallback(this)
vout.detachViews()
libvlc?.release()
}
private fun handleRtsp() {
holder = view_surface.holder
}
private val mPlayerListener: MediaPlayer.EventListener = MyPlayerListener(this)
inner class MyPlayerListener(owner: Rove3LiveVideoFragment) : MediaPlayer.EventListener {
private val mOwner: WeakReference<Rove3LiveVideoFragment>
init {
mOwner = WeakReference(owner)
}
override fun onEvent(event: MediaPlayer.Event) {
val player = mOwner.get()
when (event.type) {
MediaPlayer.Event.EndReached -> {
Log.d("MediaPlayerEVENTERRO", "MediaPlayerEndReached")
player?.releasePlayer()
}
MediaPlayer.Event.EncounteredError -> {
player?.releasePlayer()
libvlc?.release()
mMediaPlayer.stop()
mMediaPlayer.pause()
mMediaPlayer.retain()
mMediaPlayer.isSeekable
createPlayer(mFilePath)
val m = Media(libvlc, Uri.parse(mFilePath))
val cache = 1500
// m.addOption(":network-caching=$cache")
// m.addOption(":file-caching=$cache")
// m.addOption(":live-cacheing=$cache")
// m.addOption(":sout-mux-caching=$cache")
// m.addOption(":codec=mediacodec,iomx,all")
mMediaPlayer.media = m
mMediaPlayer.play()
Log.d("MediaPlayerEVENTERROR", "Media Player Error, re-try")
}
MediaPlayer.Event.Playing, MediaPlayer.Event.Paused, MediaPlayer.Event.Stopped -> {}
else -> {}
}
}
}
private fun setSize(width: Int, height: Int) {
mVideoWidth = width
mVideoHeight = height
if (mVideoWidth * mVideoHeight <= 1) return
if (holder == null || view_surface == null) return
var w = activity?.window?.decorView?.width
var h = activity?.window?.decorView?.height
val isPortrait = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
if (w != null) {
if (w > h!! && isPortrait || w < h && !isPortrait) {
val i = w
w = h
h = i
}
}
val videoAR = mVideoWidth.toFloat() / mVideoHeight.toFloat()
val screenAR = h?.toFloat()?.let { w?.toFloat()?.div(it) }
if (screenAR != null) {
if (screenAR < videoAR) if (w != null) {
h = (w / videoAR).toInt()
} else if (h != null) {
w = (h * videoAR).toInt()
}
}
holder.setFixedSize(mVideoWidth, mVideoHeight)
val lp: ViewGroup.LayoutParams = view_surface.layoutParams
if (w != null) {
lp.width = w
}
if (h != null) {
lp.height = h
}
view_surface.layoutParams = lp
view_surface.invalidate()
}
}
this code is working fine i am able to show live video using rtsp player
Some time when i fast open app then i am getting MediaPlayer.Event.EncounteredError i don't know what is reason but in that function i am trying to again restart and create media player but still my video is not playing its showing full black background in surface view . actually i want restart my live broadcast restart when ever any error coming like Hardware error or any MediaPlayer.Event.EncounteredError please help me what i am doing wrong how to restart live view .
I am using below library to show live video
**implementation "de.mrmaffen:vlc-android-sdk:2.0.6"
implementation 'org.videolan.libvlc:libvlc_options:2.0.6'**
I am currently creating a chart to display statistics.
The problem is that on the combinedChart I can't display the lineDataChart
The expected result is this
The second image is the superposition of a barChart and a lineChart but the problem is i can't use properly the scroll mode because it will only scroll on one graph.
Here is the code for the first graph
class GraphCombinedChart (context: Context) : CombinedChart(context, null, 0) {
private var chart: CombinedChart? = null
private fun dtoToBarEntry(floats: List<Float>?) : ArrayList<BarEntry> {
return ArrayList<BarEntry>().apply {
floats?.forEachIndexed { index, fl ->
add(BarEntry((index + 1).toFloat() , fl))
}
}
}
private fun dtoToEntry(floats: List<Float>?) : ArrayList<Entry> {
return ArrayList<Entry>().apply {
floats?.forEachIndexed { index, fl ->
add(Entry((index + 1).toFloat() , fl))
}
}
}
fun setupChart(
values: List<PlayerGraphStatModel>?,
landScapeMode: Boolean = false
) {
val indexPerformanceList = dtoToEntry(values?.map { it.timePlayed.toFloat() }?.plus(values.map { it.timePlayed.toFloat() }))
val timePlayedList = dtoToBarEntry( values?.map { it.indexPerf }?.plus(values.map { it.indexPerf }))
val entryData = LineData(LineDataSet(indexPerformanceList, "").apply {
color = Color.White.hashCode()
setDrawValues(true)
})
val barEntryData = BarData(BarDataSet(timePlayedList, "").apply {
color = Color.AiaRed.hashCode()
setDrawValues(false)
})
val combinedData = CombinedData()
combinedData.setData(barEntryData)
combinedData.setData(entryData)
chart = this
formatChart(combinedData, landScapeMode)
configureXAxis()
configureYAxis()
chart?.renderer = BarChartRenderer(chart, chart?.animator, chart?.viewPortHandler)
}
private fun formatChart(combinedData: CombinedData, landScapeMode: Boolean = false) {
val barWidth = 0.75f
chart?.drawOrder = arrayOf(
DrawOrder.LINE,
DrawOrder.BAR,
DrawOrder.LINE,
)
chart?.data = combinedData
chart?.description?.isEnabled = false
chart?.legend?.isEnabled = false
chart?.barData?.barWidth = barWidth
chart?.animateY(1000)
chart?.isDoubleTapToZoomEnabled = false
chart?.setScaleEnabled(false)
chart?.isHorizontalScrollBarEnabled = true
// barChart?.setVisibleXRangeMaximum(8f)
// barChart?.moveViewToX(0f)
if (!landScapeMode) {
chart?.setVisibleXRangeMaximum(8f)
chart?.moveViewToX(11f)
}
chart?.resetViewPortOffsets()
chart?.resetZoom()
chart?.notifyDataSetChanged()
}
private fun configureXAxis() {
chart?.xAxis?.apply {
setDrawGridLines(false)
setDrawAxisLine(false)
setDrawLabels(true)
position = XAxis.XAxisPosition.BOTTOM
valueFormatter = MyXAxisFormatter()
granularity = 1f
labelRotationAngle = +0f
textColor = Color.White.hashCode()
textSize = 12f
textAlignment = TEXT_ALIGNMENT_CENTER
axisMinimum = data.xMin - 0.75f
axisMaximum = data.xMax + 0.75f
disableScroll()
}
}
private fun configureYAxis() {
chart?.axisRight?.apply {
setDrawGridLines(false)
legend?.isEnabled = true
isEnabled = false
}
chart?.axisLeft?.apply {
setAxisMinValue(0f)
setAxisMaxValue(100f)
valueFormatter = MyLeftAxisFormatter()
setDrawGridLines(true)
setDrawAxisLine(true)
textColor = Color.White.hashCode()
textSize = 14f
}
}
inner class MyXAxisFormatter : IndexAxisValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
return "${value.toInt()}"
}
}
inner class MyLeftAxisFormatter : IndexAxisValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
return "${value.toInt()}"
}
}
}
I have checked and the data is not empty.
Thank you for your help!
I use MediaCodec.inputSurface and MediaMixuer for recording of my android view. Everything is good on the most of devices, but not on Huawei. For some reason it produces a video which cannot be played on this devices. But for some unknown reason 1 out of 10 times it generates a good video. Here's link to the broken video and to the normal video. It is also weird that both videos can be played on my mac laptop.
Our users reported the issue from multiple huawei models and I can confirm it from my phone: HUAWEI P8 lite 2017, android 7.0. Also it happens on new phones too with any android version.
Here's code of how I manage the recording:
/**
* Stages:
* 1. Draw canvas to bitmap
* 2. Take bitmap pixels and convert them to YUV
* 3. Write bitmap pixels as a frame to MediaCodec
* 4. Take mediaCodec and write to mediaMuxer to receive file
*/
class VideoEncoder(
val width: Int,
val height: Int,
val frameRate: Int,
val file: File,
val durationUs: Long,
val handler: Handler,
val videoRecordingFinished: () -> Unit,
val onError: (MediaCodec.CodecException) -> Unit
) : KoinComponent {
var mediaMuxer: MediaMuxer? = null
var videoCodec: MediaCodec? = null
var videoTrackIndex = 0
var surface: Surface? = null
val videoBufferInfo by lazy { MediaCodec.BufferInfo() }
var writingVideoFinished = false
private var currentFrame = 0
var audioEncoder: AudioEncoder? = null
var writingAudioFinished: Boolean
get() = audioEncoder?.writingAudioFinished ?: true
set(value) {
audioEncoder?.writingAudioFinished = value
}
var videoFormatInited: Boolean = false
val allFormatsInited: Boolean
get() = videoFormatInited && (audioEncoder?.audioFormatInited != false)
private val pendingVEncoderInfos = LinkedList<MediaCodec.BufferInfo>()
private val pendingVEncoderIndices = LinkedList<Int>()
val logger: KLogger by inject {
parametersOf("video-encoder")
}
private fun createVideoFormat(mimeType: String, desiredColorFormat: Int): MediaFormat {
val mediaFormat =
MediaFormat.createVideoFormat(mimeType, width, height)
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, ENCODING_VIDEO_BITRATE)
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, this.frameRate)
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, desiredColorFormat)
return mediaFormat
}
private fun findCorrectVideoFormat(): MediaFormat {
val mimeType = POSSIBLE_MIME_TYPES[0]
val desiredColorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
val mediaFormat = createVideoFormat(mimeType, desiredColorFormat)
val encoderForFormat =
MediaCodecList(MediaCodecList.REGULAR_CODECS).findEncoderForFormat(mediaFormat)
if (encoderForFormat == null) {
logger.info { "encoderForFormatIsNull!!! width = $width, height = $height" }
videoCodec = MediaCodec.createEncoderByType(mimeType)
} else {
videoCodec = MediaCodec.createByCodecName(encoderForFormat)
}
val codecInfo = videoCodec!!.codecInfo
if (codecInfo.isEncoder && codecInfo.supportedTypes.contains(mimeType) &&
codecInfo.getCapabilitiesForType(mimeType).colorFormats
.contains(desiredColorFormat)
) {
} else {
throw IllegalStateException("MediaCodec is wrong = ${codecInfo}")
}
val errorMessage = checkIsColorFormatSupported(mediaFormat, desiredColorFormat, mimeType)
if (errorMessage != null)
throw IllegalStateException(errorMessage)
return mediaFormat
}
//return error message if false
fun checkIsColorFormatSupported(
mediaFormat: MediaFormat,
desiredColorFormat: Int,
mimeType: String
): String? {
var colorFormats = videoCodec!!.codecInfo.getCapabilitiesForType(mimeType).colorFormats
var colorFormatSize = colorFormats.size
var counterColorFormat = 0
val colorFormatCorrect: Boolean
while (true) {
if (counterColorFormat >= colorFormatSize) {
colorFormatCorrect = false
break
}
if (colorFormats[counterColorFormat] == desiredColorFormat) {
colorFormatCorrect = true
break
}
++counterColorFormat
}
if (!colorFormatCorrect) {
var message = "NO COLOR FORMAT COMPATIBLE\\n$mediaFormat"
colorFormats = videoCodec!!.codecInfo.getCapabilitiesForType(mimeType).colorFormats
colorFormatSize = colorFormats.size
counterColorFormat = 0
while (counterColorFormat < colorFormatSize) {
val sb = StringBuilder()
sb.append(message)
sb.append("\\n")
sb.append(colorFormats[counterColorFormat])
message = sb.toString()
logger.debug { message }
++counterColorFormat
}
return message
}
return null
}
private fun printVideoCodecInfo() {
logger.debug {
val json = JSONObject()
json.put("codec_name", videoCodec!!.name)
json.put("codec_info_name", videoCodec!!.codecInfo.name)
json.put("codec_supported_types", videoCodec!!.codecInfo.supportedTypes)
json.put("output_width", width)
json.put("output_height", height)
json.toString()
}
}
#Throws(Exception::class)
fun initialize(videoAsyncEncoder: Boolean) {
val filePath = file.canonicalPath
val mediaFormat = findCorrectVideoFormat()
printVideoCodecInfo()
if (videoAsyncEncoder) {
videoCodec!!.setCallback(object : MediaCodec.Callback() {
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
}
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo
) {
pendingVEncoderIndices.add(index)
pendingVEncoderInfos.add(info)
if (allFormatsInited)
checkVideoOutputAvailable()
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
writingVideoFinished = true
e.printDebug()
onError.invoke(e)
}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
onVideoFormatChanged(format)
}
}, handler)
}
videoCodec!!.configure(
mediaFormat, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE
)
surface = videoCodec!!.createInputSurface()
mediaMuxer = MediaMuxer(filePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
}
fun initAudio(
path: String,
startTimeUs: Long,
volume: Int,
audioRecordingFinished: () -> Unit
) {
audioEncoder = AudioEncoder(
mediaMuxer!!,
handler,
durationUs,
::checkFormatsInited,
audioRecordingFinished
)
audioEncoder!!.initAudio(path, startTimeUs, volume)
audioEncoder!!.startAudioCodec()
}
fun canWriteAudio() {
audioEncoder?.canWriteAudio()
}
fun getCurrentAudioTime() = audioEncoder?.getCurrentAudioTime()
private fun onVideoFormatChanged(format: MediaFormat) {
videoTrackIndex =
mediaMuxer!!.addTrack(format)
videoFormatInited = true
checkFormatsInited()
}
fun checkFormatsInited() {
if (allFormatsInited) {
mediaMuxer!!.start()
checkVideoOutputAvailable()
}
}
#Throws(IllegalStateException::class)
fun writeToMuxerSyncMode(currentFrame: Int = -1): Boolean {
var success = false
while (videoCodec != null && mediaMuxer != null) {
val outputBufferIndex = videoCodec!!.dequeueOutputBuffer(videoBufferInfo, 0L)
logger.info {
"writeToMuxer, outputBufferIndex = ${outputBufferIndex}, bufferFlag = ${videoBufferInfo.flags}," +
" presentationTime = ${((currentFrame * 1000000L) / frameRate)}," +
" bufferInfo.size ${videoBufferInfo.size}, bufferInfo.offset ${videoBufferInfo.offset}"
}
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
onVideoFormatChanged(videoCodec!!.outputFormat)
} else {
if (outputBufferIndex < 0) {
return success
}
success = true
val bufferInfo = videoBufferInfo
if (bufferInfo.offset >= 0 && bufferInfo.size > 0) {
val outputBuffer = videoCodec!!.getOutputBuffer(outputBufferIndex)!!
outputBuffer.position(this.videoBufferInfo.offset)
outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
if (currentFrame != -1) {
if (videoBufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
success = false
else
bufferInfo.presentationTimeUs = (currentFrame * 1000000L) / frameRate
}
mediaMuxer!!.writeSampleData(
videoTrackIndex,
outputBuffer,
this.videoBufferInfo
)
}
videoCodec!!.releaseOutputBuffer(outputBufferIndex, false)
if (bufferInfo.flags.and(MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
return success
}
}
}
return success
}
private fun onVideoWritingFinished() {
writingVideoFinished = true
videoRecordingFinished.invoke()
}
private fun checkVideoOutputAvailable() {
while (pendingVEncoderIndices.size > 0 &&
pendingVEncoderInfos.size > 0 && videoCodec != null
) {
val index = pendingVEncoderIndices.removeFirst()
val info = pendingVEncoderInfos.removeFirst()
onVideoOutputAvailable(videoCodec!!, index, info)
}
}
private fun onVideoOutputAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
if (videoCodec == null)
return
if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
codec.releaseOutputBuffer(index, false)
onVideoWritingFinished()
} else {
val outputBuffer = codec.getOutputBuffer(index)!!
outputBuffer.position(info.offset)
outputBuffer.limit(info.offset + info.size)
info.presentationTimeUs = (currentFrame * 1000000L) / frameRate
if (info.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
currentFrame++
}
logger.info {
"videoOutputAvailable time ${info.presentationTimeUs}, flags ${info.flags}," +
" size ${info.size}, offset ${info.offset}"
}
mediaMuxer!!.writeSampleData(
videoTrackIndex,
outputBuffer, info
)
codec.releaseOutputBuffer(index, false)
}
}
fun startVideoCodec() {
videoCodec?.start()
}
fun stop() {
audioEncoder?.stop()
pendingVEncoderInfos.clear()
pendingVEncoderIndices.clear()
surface?.release()
surface = null
if (videoCodec != null) {
try {
videoCodec?.stop()
} catch (e: IllegalStateException) {
} finally {
videoCodec?.release()
videoCodec = null
}
}
if (mediaMuxer != null) {
try {
mediaMuxer?.release()
} catch (e: IllegalStateException) {
logger.error(e)
} finally {
mediaMuxer = null
}
}
}
fun sendEndOfStreamSurface() {
videoCodec?.signalEndOfInputStream()
if (!ThreadRecord.VIDEO_CODEC_ASYNC) {
onVideoWritingFinished()
}
}
companion object {
const val ENCODING_VIDEO_BITRATE = 12000000
val POSSIBLE_MIME_TYPES = arrayOf("video/avc", "video/hevc", "video/x-vnd.on2.vp8")
}
}
I am working on an android project in which I want to remove the item from vertical recycler view by swiping an item to right or left. As far as it comes to deleting the item, I am able to do that correctly. The issue arises after I delete the item and call notifyDataSetChanged().
Now while refreshing items, Adapter uses previous view holders to display cards. But while deleting an item I displaced some of its layouts to left and right. So when new item occupies the same view holder all the translation is preserved and
some views of the new item are thus created out of bounds of the screen(as I displaced that view holder while deleting the previous item that occupied that spot).
So my question is (solution to any one of the following will solve the issue I am facing),
Is there "free" function like c++ in Java? So that I can free that view holder.
How to make sure that recycler view doesn't reuse particular view holder?
How to reset layout of to the original state before all animations were done? So that I can just reset all translations
Thank you.
Edits:
Below are the codes for adapter and view holder if someone wants to have a look at them.
As view holder is too large to understand directly, SO here is summery for functions used in it:
init -> just set on click listener.
updateUI -> sets some more listener and set text and other fields from card.
pressDown -> is called when MotionEvent.ACTION_DOWN.
pressUP -> is called when MotionEvent.ACTION_UP.
animateViewDisappear -> listener for animation.
archive -> delete an element and tell adapter data is change.
pressMove -> sense when motion happens after clicking button.
toggleShowHideView -> change visibility of some view.
closeOpenedLayout -> Close some expanded layouts.
Adapter:
class LocalAdapter(var localCardsInfo : ArrayList<LocalModel>?,var fragment: LocalListingFragment) : RecyclerView.Adapter<LocalHolder>() {
fun refreshDataOnOrientationChange(mLocalCards : ArrayList<LocalModel>?){
if (localCardsInfo!!.size>0)
localCardsInfo!!.clear()
localCardsInfo = mLocalCards
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return localCardsInfo!!.size
}
override fun onBindViewHolder(holder: LocalHolder, position: Int) {
if (localCardsInfo!=null)
holder.updateUI(position,localCardsInfo!![position])
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalHolder {
val card_a : View = LayoutInflater.from(parent.context).inflate(R.layout.card_local_a,parent,false)
return LocalHolder(card_a,fragment)
}
}
view holder:
class LocalHolder(itemView : View,val fragment: LocalListingFragment) : RecyclerView.ViewHolder(itemView),OpenedLayoutManagerLocal{
private val TAG = "iotcontrollerapp.#Debug"
private var _xDelta: Float = 0f
private var originalDelta : Float = 0f
private var directionOut1 = false
private var directionOut = false
private var previousX = 0f
var isFavourite : Boolean = false
var isPropertyPanelOpen : Boolean = false
val deviceName : TextView = itemView.findViewById(R.id.deviceNameLocal)
val deviceRoom : TextView = itemView.findViewById(R.id.deviceRoomLocal)
val deviceOnOff : TextView = itemView.findViewById(R.id.deviceOnOffLocal)
val showHideLayout : LinearLayout = itemView.findViewById(R.id.showHideLayoutLocal)
val showHideProperties : TextView = itemView.findViewById(R.id.showHidePropertiesLocal)
val showHideButton : ImageView = itemView.findViewById(R.id.showHideButtonLocal)
val favouriteButton : ImageButton = itemView.findViewById(R.id.imageFavouriteButtonLocal)
val moveButton : MoveableViewImageButton = itemView.findViewById(R.id.imageMoveButtonLocal)
val changeFragmentToDetail : Button = itemView.findViewById(R.id.changePropertiesLocal)
val layoutForProperties : LinearLayout = itemView.findViewById(R.id.layoutForPropertyLocal)
val baseForProperties : LinearLayout = itemView.findViewById(R.id.baseForPropertyLocal)
private var model : LocalModel? = null
init {
itemView.elevation = 0f
showHideLayout.setOnClickListener({
isPropertyPanelOpen = !isPropertyPanelOpen
if (isPropertyPanelOpen){
if (openedLayoutManagerLocal != this)
openedLayoutManagerLocal?.closeOpenedLayout()
openedLayoutManagerLocal = this
showHideProperties.text = fragment.getString(R.string.close_property_panel)
showHideButton.setImageResource(R.drawable.animated_more_button_local)
}
else{
showHideProperties.text = fragment.getString(R.string.open_property_panel)
showHideButton.setImageResource(R.drawable.animated_more_button_reverse_local)
}
val mDrawable = showHideButton.drawable
if (mDrawable is Animatable)
{
(mDrawable as Animatable).start()
}
toggleShowHideView()
})
}
fun changeFavouriteButtonState(localModel: LocalModel){
isFavourite = !isFavourite
if (isFavourite){
favouriteButton.setImageResource(R.drawable.avd_heart_fill)
val a = favouriteButton.drawable
if(a is Animatable){
a.start()
}
ALL_STATIC_CONSTANTS_AND_METHODS.saveIsFavourite(fragment.activity as Context,localModel.roomName.trim() + "$" + localModel.deviceName,true)
}
else{
favouriteButton.setImageResource(R.drawable.avd_heart_break)
val a = favouriteButton.drawable
if(a is Animatable){
a.start()
}
ALL_STATIC_CONSTANTS_AND_METHODS.saveIsFavourite(fragment.activity as Context,localModel.roomName.trim() + "$" + localModel.deviceName,false)
}
}
fun updateUI(position : Int , localModel: LocalModel){
itemView.elevateLayoutLocal.visibility = View.VISIBLE
itemView.archiveLayout.visibility = View.VISIBLE
model = localModel
originalDelta = itemView.elevateLayoutLocal.x
changeFragmentToDetail.setOnClickListener({
fragment.handlerForDetail.sendEmptyMessage(position)
})
favouriteButton.setOnClickListener({
changeFavouriteButtonState(localModel)
})
moveButton.setOnTouchListener({v: View?, event: MotionEvent? ->
if (v == null || event == null) return#setOnTouchListener true
when (event.action){
MotionEvent.ACTION_UP -> {
v.performClick()
pressUP(v,event)
}
MotionEvent.ACTION_DOWN -> {
pressDown(v,event)
}
MotionEvent.ACTION_MOVE -> {
pressMove(v,event)
}
else -> {
Log.e(TAG,"Something happened")
}
}
return#setOnTouchListener true
})
isFavourite = (ALL_STATIC_CONSTANTS_AND_METHODS.getIsFavourite(fragment.activity as Context,localModel.roomName.trim() + "$" + localModel.deviceName) ?: false)
favouriteButton.setImageResource(if (isFavourite) R.drawable.vd_trimclip_heart_full else R.drawable.vd_trimclip_heart_empty)
deviceRoom.text = localModel.roomName.split("_").dropLast(1).toTypedArray().joinToString(" ")
deviceName.text = localModel.deviceName.split("_").dropLast(1).toTypedArray().joinToString(" ")
deviceOnOff.text = if (localModel.isON) fragment.getString(R.string.device_on_off_on) else fragment.getString(R.string.device_on_off_off)
val components = localModel.componentName.split(" ")
val value = localModel.value.split(" ")
val maxValue = localModel.maxValue.split(" ")
val minValue = localModel.minValue.split(" ")
if (layoutForProperties.childCount > 0)
layoutForProperties.removeAllViews()
if (components.size == value.size && minValue.size == maxValue.size && components.size == minValue.size){
for (i in 0 until components.size){
val layout : LinearLayout = LinearLayout.inflate(fragment.activity , R.layout.card_local_b , null) as LinearLayout
layout.findViewById<TextView>(R.id.propertyLocal).text = components[i].replace("_"," ")
layout.findViewById<TextView>(R.id.valueLocal).text = value[i]
layout.findViewById<TextView>(R.id.rangeLocal).text = minValue[i] + " to " + maxValue[i]
layoutForProperties.addView(layout)
}
}
}
private fun pressDown(imageButton: View, event: MotionEvent){
fragment.mLayoutManager?.setScrollEnabled(false)
openedLayoutManagerLocal?.closeOpenedLayout()
_xDelta = itemView.elevateLayoutLocal.x - event.rawX
}
private fun pressUP(imageButton: View, event: MotionEvent){
Log.e(TAG,"itemView.elevateLayoutLocal.x :: ${(itemView.elevateLayoutLocal.width / 3.toFloat()) - itemView.elevateLayoutLocal.x}")
val status = (itemView.elevateLayoutLocal.width / 3.toFloat()) < itemView.elevateLayoutLocal.x
val status1 = (itemView.elevateLayoutLocal.width / 2.toFloat()) < itemView.elevateLayoutLocal.x
itemView.elevateLayoutLocal.animate()
.x(if ((directionOut && status) || (status1 && directionOut1)) itemView.elevateLayoutLocal.width.toFloat() else originalDelta)
.setDuration(100)
.setListener(object : Animator.AnimatorListener{
override fun onAnimationCancel(animation: Animator?) {
if ((directionOut && status) || (status1 && directionOut1)) {
itemView.elevateLayoutLocal.visibility = View.GONE
val anim = ValueAnimator.ofInt(itemView.archiveLayout.measuredHeight, 0)
anim.addUpdateListener { valueAnimator ->
val `val` = valueAnimator.animatedValue as Int
val layoutParams = itemView.archiveLayout.layoutParams
layoutParams.height = `val`
itemView.archiveLayout.layoutParams = layoutParams
}
anim.addListener(animateViewDisappear(this#LocalHolder))
anim.duration = 1000
anim.start()
}else{
fragment.mLayoutManager?.setScrollEnabled(true)
}
}
override fun onAnimationEnd(animation: Animator?) {
if ((directionOut && status) || (status1 && directionOut1)) {
itemView.elevateLayoutLocal.visibility = View.GONE
val anim = ValueAnimator.ofInt(itemView.archiveLayout.measuredHeight, 0)
anim.addUpdateListener { valueAnimator ->
val `val` = valueAnimator.animatedValue as Int
val layoutParams = itemView.archiveLayout.layoutParams
layoutParams.height = `val`
itemView.archiveLayout.layoutParams = layoutParams
}
anim.addListener(animateViewDisappear(this#LocalHolder))
anim.duration = 1000
anim.start()
}else{
fragment.mLayoutManager?.setScrollEnabled(true)
}
}
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
}).start()
}
class animateViewDisappear(var i : LocalHolder) :Animator.AnimatorListener {
override fun onAnimationCancel(animation: Animator?) {
i.itemView.archiveLayout.visibility = View.GONE
i.itemView.elevateLayoutLocal.visibility = View.GONE
i.itemView.elevateLayoutLocal.x = i.originalDelta
val layoutParams = i.itemView.archiveLayout.layoutParams
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
i.itemView.archiveLayout.layoutParams = layoutParams
i.archived()
}
override fun onAnimationEnd(animation: Animator?) {
i.itemView.archiveLayout.visibility = View.GONE
i.itemView.elevateLayoutLocal.visibility = View.GONE
i.itemView.elevateLayoutLocal.x = i.originalDelta
val layoutParams = i.itemView.archiveLayout.layoutParams
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
i.itemView.archiveLayout.layoutParams = layoutParams
i.archived()
}
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
}
private fun archived(){
fragment.mLayoutManager?.setScrollEnabled(true)
if (model != null) {
ALL_STATIC_CONSTANTS_AND_METHODS.addToArchive(fragment.activity!!, model!!.roomName, model!!.deviceName)
fragment.mAdapter?.refreshDataOnOrientationChange(LocalDataService.ourInstance.getNonArchivedItems(fragment.activity!!))
}
}
private fun pressMove(imageButton: View, event: MotionEvent){
directionOut1 = itemView.elevateLayoutLocal.x >= (previousX)
directionOut = itemView.elevateLayoutLocal.x >= (previousX + 20)
previousX = itemView.elevateLayoutLocal.x
fragment.mLayoutManager?.setScrollEnabled(false)
itemView.elevateLayoutLocal.animate()
.x(event.rawX + _xDelta)
.setDuration(0)
.start()
}
fun toggleShowHideView(){
changeFragmentToDetail.visibility = if (isPropertyPanelOpen) View.VISIBLE else View.GONE
layoutForProperties.visibility = if (isPropertyPanelOpen) View.VISIBLE else View.GONE
baseForProperties.visibility = if (isPropertyPanelOpen) View.VISIBLE else View.GONE
}
override fun closeOpenedLayout() {
if (isPropertyPanelOpen){
isPropertyPanelOpen = !isPropertyPanelOpen
showHideProperties.text = fragment.getString(R.string.open_property_panel)
showHideButton.setImageResource(R.drawable.animated_more_button_reverse_local)
val mDrawable = showHideButton.drawable
if (mDrawable is Animatable)
{
(mDrawable as Animatable).start()
}
toggleShowHideView()
}
}
companion object {
var openedLayoutManagerLocal : OpenedLayoutManagerLocal? = null
}
}