Search bar appearing when swiping down in Kotlin - android

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

Related

Attempt to invoke virtual method 'java.lang.String java.lang.Object.toString()' on a null object reference slots

guys, please help me with solving the problem, I've been working on it for 3 days, I tried to make a project from the video, I did everything the same, but I have an error: NullPointerException
Full text of the error:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.Object.toString()' on a null object reference
at com.meridiane.slots.ImageViewScrolling.getValue(ImageViewScrolling.kt:23)
at com.meridiane.slots.MainActivity.eventEnd(MainActivity.kt:12)
at com.meridiane.slots.ImageViewScrolling$setValueRandom$1.onAnimationEnd(ImageViewScrolling.kt:63)
at android.view.ViewPropertyAnimator$AnimatorEventListener.onAnimationEnd(ViewPropertyAnimator.java:1111)
at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1242)
at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1484)
at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:146)
at android.animation.AnimationHandler.access$100(AnimationHandler.java:37)
at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:54)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1278)
at android.view.Choreographer.doCallbacks(Choreographer.java:1019)
at android.view.Choreographer.lambda$new$0$Choreographer(Choreographer.java:235)
at android.view.-$$Lambda$Choreographer$zXV0PrqwmpdPajenUBozqc6c8Hs.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8676)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)
your text
`class ImageViewScrolling
import android.animation.Animator
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import kotlinx.android.synthetic.main.image_view_scrolling.view.*
class ImageViewScrolling: FrameLayout {
internal lateinit var eventEnd: IEventEnd
internal var last_result = 0
internal var oldValue = 0
companion object{
private const val ANIMATION_DURATION = 150
}
val value: Int
get() = Integer.parseInt(nextImage.tag.toString())
constructor(context: Context): super(context) { init(context) }
constructor(context: Context,attrs:AttributeSet): super(context,attrs) { init(context) }
fun setEventEnd(eventEnd: IEventEnd){
this.eventEnd = eventEnd
}
private fun init(context: Context) {
LayoutInflater.from(context).inflate(R.layout.image_view_scrolling,this)
nextImage.translationY = height.toFloat()
}
fun setValueRandom(image: Int, num_rotate: Int){
currentImage.visibility = View.VISIBLE
currentImage.animate()
.translationY((-height).toFloat())
.setDuration(ANIMATION_DURATION.toLong()).start()
nextImage.translationY = nextImage.height.toFloat()
nextImage.animate().translationY(0f).setDuration(ANIMATION_DURATION.toLong())
.setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationEnd(animation: Animator) {
setImage(currentImage,oldValue%8)
currentImage.translationY = 0f
if(oldValue != num_rotate){
setValueRandom(image,num_rotate)
oldValue++
} else {
last_result = 0
oldValue = 0
setImage(nextImage,image)
currentImage.visibility = View.GONE
eventEnd.eventEnd(image%8,num_rotate)
}
}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
}).start()
}
private fun setImage(img: ImageView?, value: Int) {
if(value == Util.volf) img!!.setImageResource(R.drawable.m_roll_volk)
else if(value == Util.vulkan) img!!.setImageResource(R.drawable.m_roll_vulkan)
else if(value == Util.tigree) img!!.setImageResource(R.drawable.m_roll_tigree)
else if(value == Util.mammoth) img!!.setImageResource(R.drawable.m_roll_mammonth)
else if(value == Util.emerald) img!!.setImageResource(R.drawable.m_roll_izumrude)
else if(value == Util.bird) img!!.setImageResource(R.drawable.m_roll_ptiza)
else if(value == Util.pig) img!!.setImageResource(R.drawable.m_roll_svin)
else if(value == Util.stone) img!!.setImageResource(R.drawable.m_roll_kamen)
else if(value == Util.bear) img!!.setImageResource(R.drawable.m_roll_bear)
img!!.tag = value
last_result = value
}
}
class MainActivity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
import kotlin.random.Random
class MainActivity : AppCompatActivity(),IEventEnd {
override fun eventEnd(result: Int, count: Int) {
if (image.value == image2.value && image2.value == image3.value) {
Toast.makeText(this, "Вы выиграли большой приз", Toast.LENGTH_SHORT).show()
Common.SCORE += 300
} else if (image.value == image2.value ||
image2.value == image3.value ||
image.value == image3.value
) {
Toast.makeText(this, "Вы выиграли малый приз", Toast.LENGTH_SHORT).show()
Common.SCORE += 100
} else {
Toast.makeText(this, "Выигрыша нет, попробуй ещё", Toast.LENGTH_SHORT).show()
Common.SCORE -= 20
}
textValue.text = Common.SCORE.toString()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
image.setEventEnd(this#MainActivity)
image2.setEventEnd(this#MainActivity)
image3.setEventEnd(this#MainActivity)
buttonStart.setOnClickListener {
val nextI = Random.nextInt(6)
val rotation = Random.nextInt(15 - 5 + 1) + 5
image.setValueRandom(nextI, rotation)
image2.setValueRandom(nextI, rotation)
image3.setValueRandom(nextI, rotation)
Common.SCORE -= 10
textValue.text = Common.SCORE.toString()
}
}
}
object Common {
var SCORE = 1000
}
interface IEventEnd {
fun eventEnd(result: Int, count: Int)
}
object Util {
var volf = 0
var vulkan = 1
var tigree = 2
var mammoth = 3
var emerald = 4
var bird = 5
var pig = 6
var stone = 7
var bear = 8
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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">
<LinearLayout
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
<com.meridiane.slots.ImageViewScrolling
android:id="#+id/image"
android:layout_height="100dp"
android:layout_width="100dp"/>
<com.meridiane.slots.ImageViewScrolling
android:id="#+id/image2"
android:layout_height="100dp"
android:layout_width="100dp"/>
<com.meridiane.slots.ImageViewScrolling
android:id="#+id/image3"
android:layout_height="100dp"
android:layout_width="100dp">
</com.meridiane.slots.ImageViewScrolling>
</LinearLayout>
<TextView
android:id="#+id/textValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="55dp"
android:layout_marginTop="178dp"
android:text="TextView" />
<Button
android:id="#+id/buttonStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Button" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/currentImage"
android:src="#drawable/m_roll_volk"
android:layout_width="100dp"
android:layout_height="100dp"/>
<ImageView
android:id="#+id/nextImage"
android:src="#drawable/m_roll_volk"
android:layout_width="100dp"
android:layout_height="100dp"/>
</FrameLayout>
`
I tried view binding, but it didn't help, there is no explicit initialization and I don't know how to do it

Creating a favourite checkbox in a list, Kotlin

I'm hoping to create a checkbox for items that I have in a list.
I was looking at applying this by using a boolean value for my items i.e if attribute 'favourite' = true, then checkbox is highlighted and vice versa if false.
Unfortunately, so far I am unsure of the logic for this and how to connect it to my view (Checkbox).
My view is as follows:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
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"
android:layout_marginBottom="8dp"
android:elevation="24dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="#+id/imageIcon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:contentDescription="#string/change_hike_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/hikeName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAlignment="viewStart"
android:textSize="30sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.048"
app:layout_constraintStart_toEndOf="#+id/imageIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="A Title" />
<TextView
android:id="#+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.052"
app:layout_constraintStart_toEndOf="#+id/imageIcon"
app:layout_constraintTop_toBottomOf="#id/hikeName"
tools:text="A Description" />
<CheckBox
android:id="#+id/favourite"
style="?android:attr/starStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Adapter:
package org.wit.hikingtrails.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import org.wit.hikingtrails.databinding.CardHikeBinding
import org.wit.hikingtrails.models.HikeModel
interface HikeListener {
fun onHikeClick(hike: HikeModel)
}
class HikeAdapter constructor(private var hikes: List<HikeModel>,
private val listener: HikeListener) :
RecyclerView.Adapter<HikeAdapter.MainHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainHolder {
val binding = CardHikeBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return MainHolder(binding)
}
override fun onBindViewHolder(holder: MainHolder, position: Int) {
val hike = hikes[holder.adapterPosition]
holder.bind(hike, listener)
}
override fun getItemCount(): Int = hikes.size
class MainHolder(private val binding : CardHikeBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(hike: HikeModel, listener: HikeListener) {
binding.hikeName.text = hike.name
binding.description.text = hike.description
binding.favourite.text = hike.favourite.toString()
if (hike.image != ""){
Picasso.get()
.load(hike.image)
.resize(200, 200)
.into(binding.imageIcon)
}
binding.root.setOnClickListener { listener.onHikeClick(hike) }
}
}
}
View:
package org.wit.hikingtrails.views.hikeList
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import org.wit.hikingtrails.R
import org.wit.hikingtrails.adapters.HikeAdapter
import org.wit.hikingtrails.adapters.HikeListener
//import org.wit.hikingtrails.databinding.ActivityHikeListBinding
import org.wit.hikingtrails.main.MainApp
import org.wit.hikingtrails.models.HikeModel
import android.content.Intent
import android.view.*
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.wit.hikingtrails.databinding.ActivityHikeListBinding
import org.wit.hikingtrails.views.BaseView
import timber.log.Timber
class HikeListView : BaseView(), HikeListener {
lateinit var app: MainApp
lateinit var binding: ActivityHikeListBinding
lateinit var presenter: HikeListPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHikeListBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.toolbar.title = title
setSupportActionBar(binding.toolbar)
presenter = HikeListPresenter(this)
val layoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
updateRecyclerView()
}
// override fun showHikes(hikes: List<HikeModel>) {
// recyclerView.adapter = HikeAdapter(hikes, this)
// recyclerView.adapter?.notifyDataSetChanged()
// }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onResume() {
//update the view
super.onResume()
updateRecyclerView()
binding.recyclerView.adapter?.notifyDataSetChanged()
Timber.i("recyclerView onResume")
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item?.itemId) {
R.id.item_add -> presenter.doAddHike()
R.id.item_map -> presenter.doShowHikesMap()
R.id.logout -> presenter.doLogout()
}
return super.onOptionsItemSelected(item)
}
override fun onHikeClick(hike: HikeModel) {
presenter.doEditHike(hike)
}
private fun updateRecyclerView(){
GlobalScope.launch(Dispatchers.Main){
binding.recyclerView.adapter =
HikeAdapter(presenter.getHikes(), this#HikeListView)
}
}
}
Add favorite property to your HikeModel like this
data class HikeModel(
... code
// use mutable property bcs you will update it later when checkbox is checked or uncheck.
var favorite: Boolean = false // false by default
)
and then
fun bind(hike: HikeModel, listener: HikeListener) {
...
binding.favourite.isChecked = hike.favorite
binding.favourite.setOnClickListener {
hike.favorite = binding.favourite.isChecked
listener.onHikeClick(hike)
}
...
}

Espresso AppNotIdleException when using custom View

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())
}
}

ViewTreeObserver.OnGlobalLayoutListener on each and every recycler view items to passively resize TextView automatically

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"
/>

Android : Views is not showing, and only the background (the viewgroup) is showing

Here's my problem :
I just created AboutActivity that can be accessed by Menu from the MainActivity.
The problem is, the activity_about doesn't show me the views that consists of 2 Textview and a CircleImageView.
Here's the design view of the activity_about.xml (and preview of the issue) :
https://i.stack.imgur.com/33Rky.png
And these are the codes :
MainActivity
package com.example.amgiwork
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.lang.Exception
class MainActivity : AppCompatActivity() {
private lateinit var rvEmployees: RecyclerView
private var list: ArrayList<Employee> = arrayListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rvEmployees = findViewById(R.id.rv_employees)
rvEmployees.setHasFixedSize(true)
list.addAll(EmployeeData.listData)
showRecyclerList()
}
private fun showRecyclerList() {
rvEmployees.layoutManager = LinearLayoutManager(this)
val listHeroAdapter = ListEmployeeAdapter(list)
rvEmployees.adapter = listHeroAdapter
}
private fun showAboutActivity() {
try {
val intent = Intent(this, AboutActivity::class.java)
startActivity(intent)
finish()
}
catch (e: Exception) {
Log.w("showAboutActivity", e.message.toString())
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
setMode(item.itemId)
return super.onOptionsItemSelected(item)
}
private fun setMode(selectedMode: Int) {
when (selectedMode) {
R.id.action_employee_list -> {
}
R.id.action_about -> {
showAboutActivity()
}
}
}
}
AboutActivity
package com.example.amgiwork
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import java.lang.Exception
class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
}
private fun showMainActivity() {
try {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
catch (e: Exception) {
Log.w("showMainActivity", e.message.toString())
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
setMode(item.itemId)
return super.onOptionsItemSelected(item)
}
private fun setMode(selectedMode: Int) {
when (selectedMode) {
R.id.action_employee_list -> {
showMainActivity()
}
R.id.action_about -> {
}
}
}
}
activity_about.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:background="#color/lightYellow">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="100dp"
android:orientation="vertical"
tools:ignore="UselessParent">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/tv_dev_image"
android:layout_width="match_parent"
android:layout_height="190dp"
app:civ_border_color="#color/darkRed"
app:civ_border_width="5dp"
tools:src="#drawable/dev" />
<TextView
android:id="#+id/tv_dev_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:maxLines="2"
android:textColor="#color/darkRed"
android:textSize="32sp"
android:textStyle="bold"
tools:text="Dyaksa Hanindito" />
<TextView
android:id="#+id/tv_contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:gravity="center"
android:maxLines="2"
android:textColor="#color/darkRed"
android:textSize="19sp"
tools:text="Contact :" />
<TextView
android:id="#+id/tv_dev_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="2"
android:textColor="#color/darkRed"
android:textSize="19sp"
tools:text="dyaksa.hanindito#indosatooredoo.com" />
</LinearLayout>
</LinearLayout>
I have tested in on another device, aswell as running the project on another computer and the result is still the same.
The only wrong thing here is that you are using tools prefix in your layout incorrectly. Change it to android in CircleImageView and every TextView and everything will work fine.

Categories

Resources