I am working on a graduate project for building an App that overlays other applications. It is the same style as Facebook Messenger chathead bubble, but instead of a chat, the bubble should act as a FAB, expanding to three other buttons.
For this purpose, I believe it is necessary to have a service that runs outside of the activity by using WindowManager. However, I am struggling a lot for incorporating the XML layout file to the WindowManager.
For the time being, this is what I have:
`<?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"
android:id="#+id/overlay_service"
tools:context=".OverlayService"
android:orientation="vertical">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/mainFloatingButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:srcCompat="#drawable/ic_launcher_foreground"
tools:layout_editor_absoluteX="177dp"
tools:layout_editor_absoluteY="172dp"
tools:ignore="MissingConstraints" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/voiceButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:clickable="true"
android:focusable="true"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="#+id/mainFloatingButton"
app:layout_constraintEnd_toEndOf="#+id/mainFloatingButton"
app:srcCompat="#drawable/ic_baseline_keyboard_voice_24" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/textButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:clickable="true"
android:focusable="true"
android:visibility="invisible"
app:layout_constraintEnd_toStartOf="#+id/mainFloatingButton"
app:layout_constraintTop_toTopOf="#+id/mainFloatingButton"
app:srcCompat="#drawable/ic_baseline_text_format_24" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/videoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:clickable="true"
android:focusable="true"
android:visibility="invisible"
app:layout_constraintStart_toEndOf="#+id/mainFloatingButton"
app:layout_constraintTop_toTopOf="#+id/mainFloatingButton"
app:srcCompat="#drawable/ic_baseline_videocam_24" />
</androidx.constraintlayout.widget.ConstraintLayout>`
package com.example.tetsudai
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var canDraw = true
var intent: Intent? = null
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
canDraw = Settings.canDrawOverlays(this)
if(!canDraw && intent != null) {
startActivity(intent)
}
}
val service = Intent(this, OverlayService::class.java)
startService(service)
finish()
}
}
``package com.example.tetsudai
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.PixelFormat
import android.os.Build
import android.os.IBinder
import android.view.*
import android.widget.ImageButton
import android.widget.Toast
import com.google.android.material.floatingactionbutton.FloatingActionButton
class OverlayService : Service(), View.OnTouchListener, View.OnClickListener{
private lateinit var windowManager: WindowManager
private lateinit var params: WindowManager.LayoutParams
private lateinit var overlayButton: FloatingActionButton
override fun onCreate() {
super.onCreate()
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val layout = LayoutInflater.from(this).inflate(R.layout.overlay_service, null)
overlayButton = layout.findViewById(R.id.mainFloatingButton) as FloatingActionButton
val layoutFlag = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
}else{
WindowManager.LayoutParams.TYPE_PHONE
}
params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
layoutFlag,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
params.gravity = Gravity.TOP or Gravity.START
params.x = 0
params.y = 100
windowManager.addView(layout,params)
}
override fun onDestroy() {
super.onDestroy()
windowManager.removeView(overlayButton)
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
private var initialX = 0
private var initialY = 0
private var initialTouchX = 0.0f
private var initialTouchY = 0.0f
private var moving = false
override fun onTouch(view: View?, event: MotionEvent?): Boolean {
view!!.performClick()
when(event!!.action){
MotionEvent.ACTION_DOWN -> {
initialX = params.x
initialY = params.y
initialTouchX = event.rawX
initialTouchY = event.rawY
moving = true
}
MotionEvent.ACTION_UP -> {
moving = false
}
MotionEvent.ACTION_MOVE -> {
params.x = initialX + (event.rawX - initialTouchX).toInt()
params.y = initialY + (event.rawY - initialTouchY).toInt()
windowManager.updateViewLayout(overlayButton,params)
}
}
return true
}
override fun onClick(p0: View?) {
if(!moving) Toast.makeText(this, "Button touched", Toast.LENGTH_SHORT).show()
}
}``
I am a very beginner with Kotlin, and because of the project deadline, I couldn't grasp a more deep understanding of the language. Therefore, I really appreciate any kind of help!
Related
I have a problem when I test my login screen which has custom EditText in it. When I try run the test, the test always loading and nothing happen until I get AppNotIdleException. I'd disabled my system animation when run the test, and finally try remove my custom EditText view and the test running.
Here is my code:
fragment_login.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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="wrap_content"
tools:keep="#layout/fragment_login">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.airbnb.lottie.LottieAnimationView
android:id="#+id/lottie_auth"
android:layout_width="0dp"
android:layout_height="250dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:alpha="0"
app:layout_constraintTop_toBottomOf="#+id/tv_greeting"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="#raw/watermelon_lottie" />
<TextView
android:id="#+id/tv_greeting"
style="#style/TextAppearance.StoryApp.Headline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:alpha="0"
android:layout_marginTop="32dp"
android:layout_marginEnd="20dp"
android:text="#string/greeting2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tv_login"
style="#style/TextAppearance.StoryApp.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:alpha="0"
android:layout_marginTop="16dp"
android:text="#string/login"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/lottie_auth" />
<com.ekotyoo.storyapp.ui.EmailEditText
android:id="#+id/et_email"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="20dp"
android:alpha="0"
android:inputType="textEmailAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tv_login" />
<com.ekotyoo.storyapp.ui.PasswordEditText
android:id="#+id/et_password"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="20dp"
android:alpha="0"
android:inputType="textPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/et_email" />
<Button
android:id="#+id/btn_login"
style="#style/TextAppearance.StoryApp.Button"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="20dp"
android:alpha="0"
android:background="#drawable/bg_button"
android:text="#string/login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/et_password" />
<TextView
android:id="#+id/tv_or_signup"
style="#style/TextAppearance.StoryApp.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:alpha="0"
android:text="#string/or_signup"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/btn_login" />
<ProgressBar
android:id="#+id/loading_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
PasswordEditText.kt:
package com.ekotyoo.storyapp.ui
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.text.method.PasswordTransformationMethod
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.content.ContextCompat
import androidx.core.widget.doOnTextChanged
import com.ekotyoo.storyapp.R
class PasswordEditText : AppCompatEditText, View.OnTouchListener {
private val roundedBackground =
ContextCompat.getDrawable(context, R.drawable.bg_custom_edittext)
private lateinit var hidePasswordIcon: Drawable
private lateinit var showPasswordIcon: Drawable
private var isHidden = true
private val passwordTransformationMethod = PasswordTransformationMethod.getInstance()
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init()
}
private fun init() {
setOnTouchListener(this)
doOnTextChanged { text, _, _, _ ->
text?.let {
if (it.length < 6 && it.isNotBlank()) setError(
context.applicationContext.getString(
R.string.password_less_than_6
), null
)
}
}
hidePasswordIcon =
ContextCompat.getDrawable(context, R.drawable.ic_eye_disabled) as Drawable
showPasswordIcon = ContextCompat.getDrawable(context, R.drawable.ic_eye) as Drawable
hint = "Password"
textSize = 14F
textAlignment = View.TEXT_ALIGNMENT_VIEW_START
background = roundedBackground
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
transformationMethod = if (isHidden) {
showPasswordVisible()
passwordTransformationMethod
} else {
showPasswordHidden()
null
}
}
private fun setDrawables(
left: Drawable? = null,
top: Drawable? = null,
right: Drawable? = null,
bottom: Drawable? = null,
) {
setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom)
}
private fun showPasswordVisible() {
setDrawables(null, null, null, null)
setDrawables(null, null, showPasswordIcon, null)
}
private fun showPasswordHidden() {
setDrawables(null, null, null, null)
setDrawables(null, null, hidePasswordIcon, null)
}
override fun onTouch(view: View?, event: MotionEvent): Boolean {
if (compoundDrawables[2] != null) {
val toggleButtonStart: Float
val toggleButtonEnd: Float
var isToggleButtonClicked = false
if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
toggleButtonEnd = (hidePasswordIcon.intrinsicWidth + paddingStart).toFloat()
if (event.x < toggleButtonEnd) isToggleButtonClicked = true
} else {
toggleButtonStart = (width - paddingEnd - hidePasswordIcon.intrinsicWidth).toFloat()
if (event.x > toggleButtonStart) isToggleButtonClicked = true
}
if (isToggleButtonClicked) {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
hidePasswordIcon = ContextCompat.getDrawable(
context,
R.drawable.ic_eye_disabled
) as Drawable
showPasswordIcon =
ContextCompat.getDrawable(context, R.drawable.ic_eye) as Drawable
isHidden = !isHidden
return true
}
}
}
}
return false
}
}
LoginFragment.kt
package com.ekotyoo.storyapp.ui.login
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.ekotyoo.storyapp.R
import com.ekotyoo.storyapp.databinding.FragmentLoginBinding
import com.ekotyoo.storyapp.utils.Utils
import com.ekotyoo.storyapp.utils.ViewModelFactory
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
class LoginFragment : Fragment() {
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!!
private val viewModel: LoginViewModel by viewModels {
ViewModelFactory.getInstance(requireContext())
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentLoginBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupView()
playAnimation()
observeViewModel()
}
private fun setupView() {
binding.btnLogin.setOnClickListener {
val email = binding.etEmail.text.toString().trim()
val password = binding.etPassword.text.toString().trim()
viewModel.login(email, password)
}
binding.tvOrSignup.setOnClickListener {
findNavController().navigate(R.id.action_loginFragment_to_signupFragment)
}
validateForm()
}
private fun validateForm() {
val email = MutableStateFlow("")
val password = MutableStateFlow("")
with(binding) {
etEmail.doAfterTextChanged { text -> email.value = text.toString().trim() }
etPassword.doAfterTextChanged { text -> password.value = text.toString().trim() }
}
lifecycleScope.launch {
combine(email, password) { e, p ->
Utils.validateEmail(e) && Utils.validatePassword(p)
}.collect { isValid ->
binding.btnLogin.isEnabled = isValid
}
}
}
private fun playAnimation() {
val greeting = ObjectAnimator.ofFloat(binding.tvGreeting, View.ALPHA, 1F).setDuration(500L)
val lottie = ObjectAnimator.ofFloat(binding.lottieAuth, View.ALPHA, 1f).setDuration(500L)
val loginAlpha = ObjectAnimator.ofFloat(binding.tvLogin, View.ALPHA, 1F).setDuration(500L)
val loginTranslateX =
ObjectAnimator.ofFloat(binding.tvLogin, View.TRANSLATION_X, -100F, 0F).setDuration(500L)
val etEmailAlpha = ObjectAnimator.ofFloat(binding.etEmail, View.ALPHA, 1F).setDuration(500L)
val etEmailTranslateX =
ObjectAnimator.ofFloat(binding.etEmail, View.TRANSLATION_X, -100F, 0F).setDuration(500L)
val etPassAlpha =
ObjectAnimator.ofFloat(binding.etPassword, View.ALPHA, 1F).setDuration(500L)
val etPassTranslateX =
ObjectAnimator.ofFloat(binding.etPassword, View.TRANSLATION_X, -100F, 0F)
.setDuration(500L)
val btnLoginAlpha =
ObjectAnimator.ofFloat(binding.btnLogin, View.ALPHA, 1F).setDuration(500L)
val btnLoginTranslateX =
ObjectAnimator.ofFloat(binding.btnLogin, View.TRANSLATION_X, -100F, 0F)
.setDuration(500L)
val orSignup = ObjectAnimator.ofFloat(binding.tvOrSignup, View.ALPHA, 1F).setDuration(500L)
val login = AnimatorSet().apply { playTogether(loginAlpha, loginTranslateX) }
val email = AnimatorSet().apply { playTogether(etEmailAlpha, etEmailTranslateX) }
val password = AnimatorSet().apply { playTogether(etPassAlpha, etPassTranslateX) }
val btnLogin = AnimatorSet().apply { playTogether(btnLoginAlpha, btnLoginTranslateX) }
AnimatorSet().apply {
playSequentially(greeting, lottie, login, email, password, btnLogin, orSignup)
start()
}
}
private fun observeViewModel() {
viewModel.userModel.observe(viewLifecycleOwner) { userModel ->
if (userModel?.isLoggedIn == true) {
findNavController().navigate(R.id.action_loginFragment_to_homeFragment)
}
}
viewModel.errorMessage.observe(viewLifecycleOwner) { message ->
Utils.showToast(requireContext(), message)
}
viewModel.isLoading.observe(viewLifecycleOwner) { state ->
showLoading(state)
}
}
private fun showLoading(isLoading: Boolean) {
binding.loadingProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
MainActivityTest.kt
package com.ekotyoo.storyapp.ui
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.ekotyoo.storyapp.R
import com.ekotyoo.storyapp.utils.EspressoIdlingResource
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
#RunWith(AndroidJUnit4::class)
#LargeTest
class MainActivityTest {
#get:Rule
val activity = ActivityScenarioRule(MainActivity::class.java)
#Before
fun setUp() {
IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource)
}
#After
fun tearDown() {
IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource)
}
#Test
fun load_story() {
onView(withId(R.id.rv_story)).check(matches(isDisplayed()))
onView(withId(R.id.rv_story)).perform(
RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(
2
)
)
}
#Test
fun go_to_detail_story() {
onView(withId(R.id.rv_story)).check(matches(isDisplayed()))
onView(withId(R.id.rv_story)).perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
2,
click()
)
)
}
#Test
fun go_to_map_story() {
onView(withId(R.id.btn_map)).perform(click())
onView(withId(R.id.btn_back)).check(matches(isDisplayed()))
pressBack()
}
#Test
fun go_to_post_story() {
onView(withId(R.id.btn_add_story)).perform(click())
onView(withId(R.id.btn_change_image)).perform(click())
pressBack()
onView(withId(R.id.et_caption)).perform(typeText("Hello World"))
pressBack()
}
#Test
fun open_popup_menu() {
onView(withId(R.id.btn_menu)).perform(click())
}
#Test
fun login() {
onView(withId(R.id.et_email)).perform(typeText("email#mail.com"))
onView(withId(R.id.et_password)).perform(typeText("123456"))
onView(withId(R.id.btn_login)).perform(click())
}
}
I'm building an Android app using Kotlin, I wanted to implement a search there but I want it somehow hidden (just like the search bar is in every iPhone), which will only show up if you swipe down. Can this actually be implemented in an android app?
Try the following code:
MainActivity.kt file:
package com.realtomjoney.searchbar_app
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.os.Bundle
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GestureDetectorCompat
import com.realtomjoney.searchbar_app.databinding.ActivityMainBinding
import kotlin.math.abs
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var detector: GestureDetectorCompat
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setBindings()
detector = GestureDetectorCompat(this, PrimaryGestureListener())
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
return if (detector.onTouchEvent(event)) {
true
} else {
super.onTouchEvent(event)
}
}
inner class PrimaryGestureListener : GestureDetector.SimpleOnGestureListener() {
private val swipeThreshold = 100
private val swipeVelocityThreshold = 100
override fun onFling(
downEvent: MotionEvent?,
moveEvent: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = moveEvent?.x?.minus(downEvent!!.x) ?: 0.0F
val diffY = moveEvent?.y?.minus(downEvent!!.y) ?: 0.0F
if (abs(diffX) <= abs(diffY) &&
abs(diffY) > swipeThreshold && abs(velocityY) > swipeVelocityThreshold ) {
if (diffY > 0) {
this#MainActivity.binding.activityMainSearchViewTextInputLayout.alpha = 0f
this#MainActivity.binding.activityMainSearchViewTextInputLayout.visibility = View.VISIBLE
this#MainActivity.binding.activityMainSearchViewTextInputLayout
.animate()
.alpha(1f).setDuration(800)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
this#MainActivity.binding.activityMainSearchViewTextInputLayout.visibility =
View.VISIBLE
}
})
} else {
this#MainActivity.binding.activityMainSearchViewTextInputLayout
.animate()
.alpha(0f).setDuration(200)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
this#MainActivity.binding.activityMainSearchViewTextInputLayout.visibility =
View.GONE
}
})
}
}
return super.onFling(downEvent, moveEvent, velocityX, velocityY)
}
}
private fun setBindings() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
activity_main.xml file:
<?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"
android:id="#+id/root"
tools:context=".MainActivity">
<com.google.android.material.textfield.TextInputLayout
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"
android:id="#+id/activityMain_searchViewTextInputLayout"
app:startIconDrawable="#drawable/ic_baseline_search_24">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Feel free to adjust swipeThreshold swipeVelocityThreshold to your liking. The animations aren't the best, so I'd also recommend you change them.
GIF:
Video that helped me detect swipe gestures:
https://www.youtube.com/watch?v=j1aydFEOEA0
Hi so I got a code from Github and I customized it. The application doesn't have any navigation menus that's why I tried adding it myself(I have never made an application before so please be nice to me). So I tried following tutorials on Youtube on how to add navigation menus. I was successful in building one but when I tried to run it the hamburger icon doesn't seem to do anything. I probably messed up in the Main Activity but I can't seem to find the mistake. Please advise me. Thank you so much! The code of the application is below.
package com.adityap.flashy_createflashcards
import android.app.Activity
import android.content.Intent
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.MenuItem
import android.view.animation.GridLayoutAnimationController
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.recyclerview.widget.GridLayoutManager
import com.adityap.flashy_createflashcards.R
import kotlinx.android.synthetic.main.activity_main.*
import android.util.Log
import android.widget.Toast
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.adityap.flashy_createflashcards.adapters.DeckRecyclerListAdapter
import com.adityap.flashy_createflashcards.database.DatabaseHelper
import com.adityap.flashy_createflashcards.database.DatabaseHelperFactory
import com.adityap.flashy_createflashcards.models.DeckModel
import com.google.android.material.floatingactionbutton.FloatingActionButton
class MainActivity : AppCompatActivity() {
lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
lateinit var databaseHelper: DatabaseHelper
lateinit var recyclerView: RecyclerView
private lateinit var viewAdapter: RecyclerView.Adapter<*>
private lateinit var viewManager: RecyclerView.LayoutManager
private var swipeBackgroundColor: ColorDrawable = ColorDrawable(Color.parseColor("#d11a2a"))
private lateinit var deleteIcon: Drawable
lateinit var mDeckModelList: MutableList<DeckModel>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
databaseHelper = DatabaseHelperFactory.getDBHelper(this)
val fab = findViewById<View>(R.id.fab) as FloatingActionButton
setUpDrawerLayout()
fab.setOnClickListener {
startActivityForResult(Intent(this, CreateDeckActivity::class.java), 123)
}
fun onOptionsItemSelected(item: MenuItem): Boolean {
if (actionBarDrawerToggle.onOptionsItemSelected(item)) {
return true
}
return super.onOptionsItemSelected(item)
}
mDeckModelList = mutableListOf()
mDeckModelList.addAll(databaseHelper.readDeck())
viewManager = LinearLayoutManager(this)
viewAdapter = DeckRecyclerListAdapter(this, mDeckModelList)
deleteIcon = ContextCompat.getDrawable(this, R.drawable.ic_baseline_delete_24)!!
recyclerView = findViewById<RecyclerView>(R.id.recyclerView).apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter
}
/* mDeckListAdapter = DeckListAdapter(this, mDeckModelList!!)
listview.adapter = mDeckListAdapter*/
/* listview.onItemClickListener = OnItemClickListener { _, _, position, _ ->
val intent = Intent(this, ReviewDeckActivity::class.java)
intent.putExtra("Deck", mDeckModelList!![position])
startActivity(intent)
}*/
val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, postiion: Int) {
(viewAdapter as DeckRecyclerListAdapter).removeItem(viewHolder as DeckRecyclerListAdapter.CardHolder)
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean) {
val itemView = viewHolder.itemView
val iconMargin = (itemView.height - deleteIcon.intrinsicHeight) /2
if(dX>0){
swipeBackgroundColor.setBounds(itemView.left,itemView.top,dX.toInt(),itemView.bottom)
deleteIcon.setBounds(itemView.left + iconMargin,
itemView.top + iconMargin,
itemView.left + iconMargin + deleteIcon.intrinsicWidth,
itemView.bottom - iconMargin )
}else {
swipeBackgroundColor.setBounds(itemView.right + dX.toInt(),itemView.top, itemView.right,itemView.bottom)
deleteIcon.setBounds(itemView.right-iconMargin-deleteIcon.intrinsicWidth,
itemView.top + iconMargin,
itemView.right-iconMargin,
itemView.bottom - iconMargin)
}
swipeBackgroundColor.draw(c)
c.save()
if(dX > 0 )
c.clipRect(itemView.left,itemView.top, dX.toInt(), itemView.bottom)
else
c.clipRect(itemView.right+dX.toInt(),itemView.top,itemView.right, itemView.bottom)
deleteIcon.draw(c)
c.restore()
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}
val itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
}
fun setUpDrawerLayout() {
setSupportActionBar(main_toolbar)
actionBarDrawerToggle = ActionBarDrawerToggle(this, mainDrawer, R.string.app_name, R.string.app_name)
actionBarDrawerToggle.syncState()
navigationView.setNavigationItemSelectedListener {
val intent = Intent(this, ProfileActivity::class.java)
startActivity(intent)
mainDrawer.closeDrawers()
true
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == 123) {
mDeckModelList.clear()
mDeckModelList.addAll(databaseHelper.readDeck())
viewAdapter.notifyDataSetChanged()
}
}
}
Here is my activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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"
android:id="#+id/mainDrawer"
android:background="#drawable/gradientbackground"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/gradientbackground"
tools:context="com.adityap.flashy_createflashcards.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/main_toolbar"
style="#style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimaryButtonBg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="#drawable/ic_hamburger"
app:popupTheme="#style/AppTheme.PopupOverlay"
app:title="#string/app_name" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="415dp"
android:layout_height="732dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/appBarLayout"
app:layout_constraintVertical_bias="0.0" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginStart="730dp"
android:layout_marginLeft="730dp"
android:contentDescription="TODO"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/recyclerView"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="#drawable/ic_add"
app:useCompatPadding="true" />
<ImageView
android:id="#+id/imageView4"
android:layout_width="match_parent"
android:layout_height="500dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="#drawable/ic_undraw_lost_online_re_upmy" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.navigation.NavigationView
android:id="#+id/navigationView"
android:layout_gravity="start"
android:layout_width="wrap_content"
app:headerLayout="#layout/drawer_header"
app:menu="#menu/drawer_menu"
android:layout_height="match_parent">
</com.google.android.material.navigation.NavigationView>
</androidx.drawerlayout.widget.DrawerLayout>
Here is my drawer_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Profile" />
<item android:title="Follow us" />
<item android:title="Rate us" />
</menu>
Please help, I'm just new to android studio and kotlin, thank you so much.
Navigation menu should have menu items like this:
<item
android:id="#+id/home"
android:checked="true"
android:icon="#drawable/ic_home"
android:title="#string/home" />
And do this in the main Activity code:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
NavigationView navView = findViewById(R.id.nav_view);
drawer = findViewById(R.id.drawer);
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.open, R.string.close);
drawer.addDrawerListener(toggle);
navView.setNavigationItemSelectedListener(this);
}
Then add this callback to listen for menu item clicks:
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
}
This last part is very important:
#Override
protected void onPostCreate(Bundle state) {
super.onPostCreate(state);
toggle.syncState();
}
The SO post is useful in addition to this one.
I have done some code which is working fine everywhere except settings app -> accessibility menu or device admin apps menu
I need to display overlay in settings -> accessibility and device admin apps menu
I have a application where i see this type of overlay you can see below image
First image from settings app
Second image from settings app
You can see above second image when i open -> device admin app -> then my app overlay got hidden but another app overlay is still visible I don't know why?
Please help me guys to fix this issue??
**AndridManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xxxxx">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />
<application
.....
.....
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".OverlayService" />
</application>
</manifest>
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.os.IBinder
import android.view.*
class OverlayService : Service() {
private var windowManager: WindowManager? = null
private var view: View? = null
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
val layoutParamsType: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
val params = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.MATCH_PARENT
height = WindowManager.LayoutParams.WRAP_CONTENT
type = layoutParamsType
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
format = PixelFormat.TRANSLUCENT
}
params.gravity = Gravity.CENTER or Gravity.START
val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
view = inflater.inflate(R.layout.overlay_view, null)
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
windowManager!!.addView(view, params)
}
override fun onDestroy() {
super.onDestroy()
if (view != null) {
windowManager!!.removeView(view)
view = null
}
}
}
MainActivity.kt
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
if (Settings.canDrawOverlays(this)) {
startService(Intent(this, OverlayService::class.java))
} else {
startManageDrawOverlaysPermission()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_DRAW_OVERLAY_PERMISSION -> {
if (Settings.canDrawOverlays(this)) {
startService(Intent(this, OverlayService::class.java))
} else {
Toast.makeText(this, "Permission is not granted!", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun startManageDrawOverlaysPermission() {
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, "package:${packageName}".toUri()).apply {
startActivityForResult(this, REQUEST_CODE_DRAW_OVERLAY_PERMISSION)
}
}
companion object {
private const val REQUEST_CODE_DRAW_OVERLAY_PERMISSION = 5
}
}
activity_main.xml
<?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">
<androidx.appcompat.widget.AppCompatButton
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show !"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
overlay_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#00897B"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Overlay Example View"
android:textColor="#color/white" />
</LinearLayout>
I have a plain basic recycler view and its adapter.In the single recycler view row, I got a textView which are floating numbers.The textView width length shall fixed in proportional sizes, i.e. 30 % of the screen.I want to put OnGlobalLayoutListener to auto resize text downward passively whenever the text exceed over the separator lineso that it fits in the textView box prepared.I don't want WRAP the numbers
So I add OnGlobalLayoutListener on each ViewHolder and expect to detect & tracing each row items if the costTextView is Overlapped to the separator line.
However what happened is it does not resize all rows, but part of items that exposed on user screen only. Example 4-7 does not affected, 8-12 is affected, so on.
MainFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.elliot.financetracker.databinding.MainFragmentBinding
class MainFragment : Fragment() {
companion object {
private val TAG = "HomeFragment"
}
private lateinit var thisView: View
private lateinit var binding: MainFragmentBinding
private var cashList: ArrayList<Cash> = ArrayList()
private lateinit var recyclerView: RecyclerView
private lateinit var mAdapter: CashAdapter
init {
cashList = prepareMovieData()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding =
DataBindingUtil.inflate<MainFragmentBinding>(
inflater,
R.layout.main_fragment, container, false
)
thisView = binding.root
return thisView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView = binding.rvCash
mAdapter = CashAdapter(cashList, action)
val mLayoutManager: RecyclerView.LayoutManager = LinearLayoutManager(thisView.context)
recyclerView.layoutManager = mLayoutManager
recyclerView.itemAnimator = DefaultItemAnimator()
recyclerView.adapter = mAdapter
}
private val action = object : CashAdapter.ClickedListener {
override fun onItemClick(cash: Cash) {
val bundle = bundleOf("cash" to cash)
this#MainFragment.findNavController().navigate(R.id.edit_frag, bundle)
}
}
private val actionAddNewRecord = object : View.OnClickListener {
override fun onClick(v: View?) {
val arg = Bundle()
arg.putInt("sss", 43)
this#MainFragment.findNavController().navigate(R.id.edit_frag, arg)
}
}
private fun prepareMovieData(): ArrayList<Cash> {
for (i in 0..29) {
cashList.add(
Cash(
"-2000000000000000000000000.00",
"Action & Adventure $i"
)
)
}
return cashList
}
}
CashAdapter.kt
import android.graphics.Rect
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.elliot.financetracker.CashAdapter.CashViewHolder
import com.elliot.financetracker.databinding.CashListRowBinding
import com.elliot.financetracker.utils.*
//import com.example.recyclerviewbinding.databinding.MovieListRowBinding;
class CashAdapter(
private val cashList: List<Cash>,
private val clickedAction: ClickedListener
) : RecyclerView.Adapter<CashViewHolder>() {
companion object {
private var TAG = "CashAdapter"
}
inner class CashViewHolder(private val binding: CashListRowBinding) :
RecyclerView.ViewHolder(binding.root) {
var positionss: Int = -1
fun bind(cash: Cash, clickedAction: ClickedListener, positionss: Int) {
this.positionss = positionss
binding.cash = cash
binding.cvCashView.setOnClickListener {
clickedAction.onItemClick(binding.cash!!)
}
// ---------- NOTE HERE I ADD GLOBAL LAYOUT LISTENER ---------------------------------------------
this.binding.cvCashView.viewTreeObserver.addOnGlobalLayoutListener(globalObserver)
binding.executePendingBindings()
}
private val globalObserver = object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val overlapped = isOverlapped(binding.tvItemCost, binding.separatorCost)
if (overlapped) {
Log.d(CashAdapter.TAG, "${CashAdapter.TAG}: isOverlapping : $positionss")
binding.tvItemCost.textSize = resizeTextDown(binding.tvItemCost)
// always assumed values in SP units
} else {
Log.d(CashAdapter.TAG, "${CashAdapter.TAG}: isNotOverlapping : $positionss")
}
}
}
private fun resizeTextDown(target: View): Float {
val t = target as TextView
val z = t.textSize
val a = z.pxToSp()
val b = a - 2.0F
// leave it as SP unit will do**
return b
}
}
interface ClickedListener {
fun onItemClick(cash: Cash)
}
fun isOverlapped(tvItemCost: View, g20: View): Boolean {
val firstPosition = IntArray(2)
val secondPosition = IntArray(2)
tvItemCost.getLocationOnScreen(firstPosition)
g20.getLocationOnScreen(secondPosition)
val rectFirstView = Rect(
firstPosition[0],
firstPosition[1],
firstPosition[0] + tvItemCost.measuredWidth,
firstPosition[1] + tvItemCost.measuredHeight
)
val rectSecondView = Rect(
secondPosition[0],
secondPosition[1],
secondPosition[0] + g20.measuredWidth,
secondPosition[1] + g20.measuredHeight
)
return rectFirstView.intersect(rectSecondView)
}
// plain basic recycler view adapter's methods
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CashViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CashListRowBinding.inflate(layoutInflater, parent, false)
return CashViewHolder(binding)
}
override fun onBindViewHolder(holder: CashViewHolder, position: Int) {
val cash = cashList[position]
holder.bind(cash, clickedAction, position)
}
override fun getItemCount(): Int {
return cashList.size
}
}
cash_list_row.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="cash"
type="com.elliot.financetracker.Cash" />
</data>
<androidx.cardview.widget.CardView
android:id="#+id/cv_cash_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:cardBackgroundColor="#color/colorLightGreen"
app:cardElevation="10dp"
app:cardCornerRadius="#dimen/default_card_corner_radius"
app:cardUseCompatPadding="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:id="#+id/tv_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="#{cash.description}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="#id/g_20"
app:layout_constraintTop_toBottomOf="#id/tv_date"
tools:text="Item description Item description Item description Item description" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/g_20"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.7"/>
<View
android:id="#+id/separator_cost"
android:layout_width="1dp"
android:layout_height="0dp"
android:padding="20dp"
android:background="#android:color/darker_gray"
app:layout_constraintLeft_toRightOf="#id/g_20"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="#+id/tv_item_cost"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="#{cash.amountString}"
android:gravity="center_vertical"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="RM -2000000000.00" />
<!-- removed out ...........-->
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
This can be perfectly solved by the auto resizing feature of TextView
use something like this
<TextView
android:layout_width="100dp"
android:layout_height="40dp"
android:maxLines="1"
android:text="hello world google"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="1sp"
app:autoSizeMaxTextSize="25sp"
android:gravity="center_vertical"
/>