How to add textview to ConstraintLayout programatically? - android

There's similar questions, but I haven't been able to find any as of yet that get into the basics.
I'm making a to-do list application and I want it so that when I click the plus button, the user is asked for text and the item is displayed on the screen. Once the text is recieved from the user, I'm not sure how you put this into a textview. add that textview into a constraintlayout(activity_main.xml) and align it with another textview (menutitletext) so that it doesn't default to 0,0. I haven't been able to add code since I genuinely don't know where to start - the answers either are adding imageviews, are in Kotlin or are using a relativelayout. I'd appreciate any help on this.
Someone asked for my design - I think this is what you wanted ?
<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">
<View
android:id="#+id/divider"
android:layout_width="393dp"
android:layout_height="2dp"
android:layout_marginTop="28dp"
android:background="#000000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/menuaddbutton" />
<ImageButton
android:id="#+id/menusettingsbutton"
android:layout_width="65dp"
android:layout_height="55dp"
android:layout_marginStart="11dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="20dp"
android:src="#drawable/test1w"
app:layout_constraintBottom_toTopOf="#+id/divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="#+id/menutrashbutton"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.235" />
<ImageButton
android:id="#+id/menutrashbutton"
android:layout_width="60dp"
android:layout_height="65dp"
android:layout_marginStart="12dp"
android:layout_marginTop="30dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toTopOf="#+id/divider"
app:layout_constraintStart_toEndOf="#+id/menuaddbutton"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="#drawable/test1d" />
<ImageButton
android:id="#+id/menuaddbutton"
android:layout_width="78dp"
android:layout_height="59dp"
android:layout_marginStart="22dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="156dp"
android:layout_marginBottom="20dp"
android:background="#android:color/background_light"
app:layout_constraintBottom_toTopOf="#+id/divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="#+id/menueditbutton"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
app:srcCompat="#drawable/test1z" />
<ImageButton
android:id="#+id/menueditbutton"
android:layout_width="59dp"
android:layout_height="59dp"
android:layout_marginStart="80dp"
android:layout_marginTop="30dp"
android:layout_marginBottom="20dp"
android:background="#android:color/background_light"
android:onClick="edittitle"
app:layout_constraintBottom_toTopOf="#+id/divider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="#drawable/test1f" />
<TextView
android:id="#+id/menutitletext"
android:layout_width="366dp"
android:layout_height="36dp"
android:layout_marginStart="12dp"
android:layout_marginTop="15dp"
android:gravity="center_horizontal"
android:text="#string/menutitleempty"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/divider" />
</androidx.constraintlayout.widget.ConstraintLayout>

You can use same concept as in here https://stackoverflow.com/a/40527407/3904645
It adds views to ConstraintLayout programmatically and aligns them with programmatic constraints. This is what you need I believe.
For your case
set.connect(childView.getId(), ConstraintSet.TOP, parentLayout.getId(),
ConstraintSet.TOP, 60);
change parentLayout.getId() with the last list item that you have added. You can track what is the last view with some instance variable.
class MainActivity : AppCompatActivity() {
lateinit var parentLayout: ConstraintLayout
var lastView: View? = null
var numberIfViews = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
parentLayout = findViewById<View>(R.id.constraint_layout) as ConstraintLayout
lastView = parentLayout
}
fun addTextView(text: String) {
val set = ConstraintSet()
val childView = TextView(this)
childView.id = View.generateViewId()
childView.text = text
parentLayout.addView(childView, numberIfViews)
set.clone(parentLayout)
set.connect(
childView.id,
ConstraintSet.TOP,
lastView!!.id,
ConstraintSet.TOP,
60
)
set.applyTo(parentLayout)
numberIfViews++
lastView = childView
}
}
Have not tested it but it should work

ConstraintLayout parentLayout = (ConstraintLayout)findViewById(R.id.mainConstraint);
TextView tt= new TextView (this);
// set view id, else getId() returns -1
childView.setId(View.generateViewId());
layout.addView(tt, 0);
you can try this way

Related

How to change background color from fragment using xml file?

I would like to change color of one element in recyclerview on action(for example swipe, but it works fine).
I have used:
viewHolder!!.itemView.setBackgroundColor(Color.GRAY)
It works like on screen, because I have different layout like layout_content.
Gray coolor in wrong place
When I add line like below color doesn't change at all. Please notice, that I had to implement another binding with other layout which uses another .xml file to get some preferences.
layoutBinding.vContent.frameLayout.setBackgroundColor(Color.GREY)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:id="#+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/bg_list_item_incoming_leads"
android:paddingBottom="#dimen/spacing_2"
app:cardCornerRadius="#dimen/spacing_8">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton
android:id="#+id/btnClose"
style="?android:borderlessButtonStyle"
android:layout_width="#dimen/spacing_32"
android:layout_height="#dimen/spacing_32"
android:layout_gravity="end"
android:layout_marginTop="20dp"
android:layout_marginEnd="#dimen/spacing_16"
android:contentDescription="#string/notification_list_close_button_content_description"
android:src="#drawable/ic_icon_grey_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="TouchTargetSizeCheck" />
<TextView
android:id="#+id/tvLeadFullName"
style="#style/Regular.13"
android:layout_width="wrap_content"
android:layout_height="27dp"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="2dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/tvSimpleText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvInformation"
app:layout_constraintVertical_chainStyle="spread_inside"
tools:text="%leadfullname" />
<TextView
android:id="#+id/tvProductNotification"
style="#style/Regular.13"
android:layout_width="wrap_content"
android:layout_height="27dp"
android:layout_marginStart="6dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="2dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#+id/tvSimpleText"
app:layout_constraintTop_toBottomOf="#+id/tvInformation"
app:layout_constraintVertical_chainStyle="spread_inside"
tools:text="%product" />
<TextView
android:id="#+id/tvSimpleText"
style="#style/Regular.13"
android:layout_width="wrap_content"
android:layout_height="27dp"
android:layout_marginStart="6dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="2dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/tvProductNotification"
app:layout_constraintStart_toEndOf="#+id/tvLeadFullName"
app:layout_constraintTop_toBottomOf="#+id/tvInformation"
app:layout_constraintVertical_chainStyle="spread_inside"
tools:text="-" />
<TextView
android:id="#+id/tvInformation"
style="#style/Regular.15"
android:layout_width="211dp"
android:layout_height="27dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread_inside"
tools:text="You received a new Lead" />
<TextView
android:id="#+id/tvData"
style="#style/Regular.13"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="10dp"
android:textColor="#color/black"
app:layout_constraintEnd_toStartOf="#+id/btnClose"
app:layout_constraintTop_toTopOf="parent"
tools:text="11 Oct 2021" />
<TextView
android:id="#+id/tvTime"
style="#style/Regular.13"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/btnClose"
tools:text="hh:ss" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<solid
android:color="#color/white">
</solid>
<corners
android:topRightRadius="#dimen/spacing_8"
android:topLeftRadius="#dimen/spacing_8"
android:bottomLeftRadius="#dimen/spacing_8"
android:bottomRightRadius="#dimen/spacing_8">
</corners>
</shape>
How to have access to .xml file and change background from fragment directly to bg_list_item_incoming_leads.xml maybe that would be a solution?
EDIT, additional information:
BaseFragment
abstract class BaseFragmentBindings<VB : ViewBinding, LB : ViewBinding, VM : BaseViewModel>(private val inflate: Inflate<VB>, private val secondInflate: Inflate<LB>) :
Fragment() {
private var binding: VB? = null
protected val layout: VB
get() = binding!!
private var bindingLayout: LB? = null
protected val layoutBinding: LB
get() = bindingLayout!!
abstract val vm: VM
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = inflate.invoke(inflater, container, false)
bindingLayout = secondInflate.invoke(inflater, container, false)
return binding!!.root
}
fragment
#AndroidEntryPoint
class NotificationListFragment :
BaseFragmentBindings<FragmentNotificationListBinding, LayoutListItemNotificationBinding, NotificationListViewModel>
(FragmentNotificationListBinding::inflate, LayoutListItemNotificationBinding::inflate) {
override val vm: NotificationListViewModel by viewModels()
...
}
try below snippet, get reference to background, which is Drawable, check if its ColorDrawable (in your case it is set only in XML then it will always be) and if is then use class casting and set new color, other params should stay unchanged (like rounded corners set with <corners tag, btw. app:cardCornerRadius="#dimen/spacing_8" in main XML set for FrameLayout is no-op, it was left there probably when this item was a CardView)
Drawable background = layoutBinding.vContent.frameLayout.getBackground();
if (background instanceof ColorDrawable)
((ColorDrawable) background).setColor(Color.GRAY);

View Binding Both Activity and Bottom Bar

I am setting up View Binding in my project, and I ran into an issue. I tried to look for documentation regarding this under without luck.
I have my activity, and I have set up View Binding for this:
class AeropressActivity : AppCompatActivity() {
private lateinit var binding: ActivityAeropressBinding
private lateinit var bottomSheetBehavior: BottomSheetBehavior<ConstraintLayout>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAeropressBinding.inflate(layoutInflater)
setContentView(binding.root)
setupBottomSheet()
onClickListeners()
}
private fun setupBottomSheet() {
bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.layout_bottom_sheet))
// OnClickListener for bottomSheetBehavior
bottomSheetBehavior.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
}
}
)
}
After setting this up, I want to use View Binding for my BottomSheet, as it has some buttons and a Chronometer that I want to add onClickListeners to.
I have added the
lateinit var bindingBottomSheet: LayoutBottomSheetBinding
but inflating this does nothing:
bindingBottomSheet = LayoutBottomSheetBinding.inflate(layoutInflater)
What am I missing to get this to work? Is this even possible?
Edit:
The BottomSheet layout looks like this:
<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:id="#+id/layout_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="140dp"
app:behavior_peekHeight="50dp"
tools:context=".BottomSheetActivity"
app:behavior_hideable="false"
android:background="#drawable/background_bottom_bar"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<ImageView
android:id="#+id/image_view_button_home"
android:src="#drawable/ic_home"
android:layout_width="40dp"
android:layout_height="35dp"
android:layout_marginStart="24dp"
android:layout_marginTop="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="#+id/image_view_button_timer"
android:layout_width="40dp"
android:layout_height="35dp"
android:layout_marginTop="10dp"
android:src="#drawable/ic_stopwatch"
app:layout_constraintLeft_toRightOf="#id/image_view_button_home"
app:layout_constraintRight_toLeftOf="#id/image_view_button_info"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:src="#drawable/ic_info"
android:id="#+id/image_view_button_info"
android:layout_width="40dp"
android:layout_height="35dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toRightOf="#id/image_view_button_timer"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button_bottom_reset"
android:layout_width="100dp"
android:layout_height="50dp"
android:text="Reset"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/image_view_button_info" />
<Chronometer
android:gravity="center_horizontal"
android:textSize="48sp"
android:textColor="#color/white"
android:format="%s"
android:id="#+id/chronometer_bottom_bar"
android:layout_width="0dp"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:layout_height="60dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/button_bottom_start"
app:layout_constraintStart_toEndOf="#id/button_bottom_reset"
app:layout_constraintTop_toBottomOf="#id/image_view_button_timer" />
<Button
android:id="#+id/button_bottom_start"
android:layout_width="100dp"
android:layout_height="40dp"
android:text="Start"
app:layout_constraintBottom_toTopOf="#id/button_bottom_stop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/image_view_button_home" />
<Button
android:id="#+id/button_bottom_stop"
android:layout_width="100dp"
android:layout_height="40dp"
android:text="Stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/button_bottom_start" />
</androidx.constraintlayout.widget.ConstraintLayout>
The BottomSheet has its own layout which is added to the activity's layout by using the .
I want to use View Binding for my BottomSheet, as it has some buttons and a Chronometer that I want to add onClickListeners to.
So, you can't access the underlying views of the BottomSheet from the AeropressActivity
First make sure that the BottomSheet layout is wrapped in <layout></layout> tag.
Then make sure to have an id to the <include> so that it allows you access the BottomSheet layout using data binding.
AeropressActivity layout:
<layout>
.
.
<include
android:id="#+id/bottom_sheet"
layout="#layout/bottom_sheet" />
</layout>
Then to access buttons in the BottomSheet layout:
bindingBottomSheet.bottomSheet.myButtonId
Assuming that the button id is: my_button_id

Can't change GridView dynamically

I'm developing an app. In this app there's an alertdialog that opens when you click a point.
In this alert dialog there are two edittext, a gridview and a button. The gridview has associated a BaseAdapter that convert Uris to IconView (a class that extends ImageView) using Glide.
I want to set a maximum height of 350dp, but if there is just one row (there'll be always one image) I want the gridview to adapt.
I tried this way: How to have a GridView that adapts its height when items are added
But it doesn't work. So what I've tried is to count the number of elements and if there's is more than 2, set 350dp of height if there is less than 2, set 175dp. But it doesnt work.
The alert dialog layout:
<?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:orientation="vertical">
<EditText
android:id="#+id/puntoName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:fontFamily="sans-serif"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/puntoDescripcion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="1dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:maxHeight="100dp"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/puntoName" />
<GridView
android:id="#+id/photo_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:horizontalSpacing="6dp"
android:numColumns="2"
android:verticalSpacing="5dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/puntoDescripcion"></GridView>
<Button
android:id="#+id/cerrar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cerrar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/photo_grid" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is the code I'm using in order to change GridView height:
fun checkSize() {
val popUp: View = fragmentCaller.layoutInflater.inflate(R.layout.popup,null)
var photogrid: GridView = popUp.findViewById(R.id.photo_grid)
var params: ViewGroup.LayoutParams = photogrid.layoutParams
var tam: Int = 175
if (adapter.getDataSource().size>2) {
tam = DpToPixels(350)
}
photogrid.layoutParams.height = tam
photogrid.requestLayout()
}
private fun DpToPixels(dp: Int) : Int {
val escala: Float = fragmentCaller.requireContext().resources.displayMetrics.density;
var tam: Int = (dp * escala + 0.5f).toInt()
return tam
}
The size of the gridView is always 0. I don't know why. I just want to set android:layout_height programmatically. I call checkSize() method after I call Dialog.show() and everytime I do changes in the elements of adapter.
Thanks.

why adding support map fragment xml will slow down my fragment to show?

I have a constraint layout inside a scroll view. and inside that constraint layout I have a support map fragment.
I am trying to find what makes my app render slowly. it takes around 1.5 second to show a fragment that contains a map (support map fragment). I mean, when I replace fragment A to fragment B (that has map) it will takes too long, after 1-2 second, then the fragment B will appear on the screen.
here is my xml
<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="match_parent"
android:background="#android:color/white" android:id="#+id/scrollView_event_detail">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".Fragments.Reusable.EventDetailFragment"
android:id="#+id/constraintLayout_event_detail_destination">
<ImageView
android:layout_width="0dp"
android:layout_height="0dp" tools:src="#tools:sample/backgrounds/scenic[12]"
android:id="#+id/imageView_event_detail_blurred_poster"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:scaleType="centerCrop" app:layout_constraintDimensionRatio="1:1"/>
<ImageView
android:layout_width="0dp"
android:layout_height="0dp" tools:src="#tools:sample/backgrounds/scenic[12]"
android:id="#+id/imageView_event_detail_poster"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintDimensionRatio="1:1"
android:scaleType="fitCenter"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content" android:id="#+id/textView_event_detail_event_title"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="#+id/imageView_event_detail_blurred_poster"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="16dp" android:maxLines="2"
android:textSize="18sp" tools:text="Kajian Bulanan: Memahami Adab Para Penuntut Ilmu"/>
<View
android:layout_width="0dp"
android:layout_height="0.5dp" android:id="#+id/line1_view_event_detail" android:background="#696C79"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="#+id/textView_event_detail_event_title"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="16dp"/>
<Button
android:text="Hadir"
android:layout_width="0dp"
android:layout_height="50dp" android:id="#+id/button_search_event_search"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="#+id/line1_view_event_detail"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="16dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"
android:background="#drawable/rounded_button"
android:textColor="#ffffff"/>
<View
android:layout_width="0dp"
android:layout_height="0.5dp" android:id="#+id/line2_view_event_detail" android:background="#696C79"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="#+id/button_search_event_search"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="16dp"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#drawable/ic_date_time"
android:id="#+id/imageView_event_detail_icon_dateTime"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="#+id/line2_view_event_detail" android:layout_marginTop="16dp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/textView_event_detail_date_label"
app:layout_constraintTop_toTopOf="#+id/imageView_event_detail_icon_dateTime"
app:layout_constraintBottom_toBottomOf="#+id/imageView_event_detail_icon_dateTime"
app:layout_constraintStart_toEndOf="#+id/imageView_event_detail_icon_dateTime"
android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp" tools:text="Selasa, 23 Desember 2018 - Rabu, 24 Desember 2018"
android:maxLines="1"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/textView_event_detail_time_label"
app:layout_constraintTop_toBottomOf="#+id/textView_event_detail_date_label"
app:layout_constraintStart_toStartOf="#+id/textView_event_detail_date_label"
tools:text="17.00 - 21.00 WIB"
android:maxLines="1"
android:layout_marginTop="4dp" android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#drawable/ic_place"
android:id="#+id/imageView_event_detail_icon_address"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="#+id/textView_event_detail_time_label"
android:layout_marginTop="16dp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/textView_event_detail_venue_label"
app:layout_constraintTop_toTopOf="#+id/imageView_event_detail_icon_address"
app:layout_constraintBottom_toBottomOf="#+id/imageView_event_detail_icon_address"
app:layout_constraintStart_toEndOf="#+id/imageView_event_detail_icon_address"
android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp" tools:text="Universitas Gadjah Mada"
android:maxLines="1"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/textView_event_detail_address_label"
app:layout_constraintTop_toBottomOf="#+id/textView_event_detail_venue_label"
app:layout_constraintStart_toStartOf="#+id/textView_event_detail_venue_label"
tools:text="Jalan Grafika no.1. Sinduadi Sleman. Yogyakarta"
android:maxLines="1"
android:layout_marginTop="4dp" android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#drawable/ic_person"
android:id="#+id/imageView_event_detail_icon_speaker"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="#+id/textView_event_detail_address_label" android:layout_marginTop="16dp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/textView_event_detail_speaker_label"
app:layout_constraintTop_toTopOf="#+id/imageView_event_detail_icon_speaker"
app:layout_constraintBottom_toBottomOf="#+id/imageView_event_detail_icon_speaker"
app:layout_constraintStart_toEndOf="#+id/imageView_event_detail_icon_speaker"
android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp" tools:text="Pembicara: Ustadz Khalid Basalamah"
android:maxLines="1"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#drawable/ic_monetization"
android:id="#+id/imageView_event_detail_icon_price"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="#+id/textView_event_detail_speaker_label"
android:layout_marginTop="16dp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/textView_event_detail_price_label"
app:layout_constraintTop_toTopOf="#+id/imageView_event_detail_icon_price"
app:layout_constraintBottom_toBottomOf="#+id/imageView_event_detail_icon_price"
app:layout_constraintStart_toEndOf="#+id/imageView_event_detail_icon_price"
android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp" tools:text="Rp 50.000"
android:maxLines="1"/>
<View
android:layout_width="0dp"
android:layout_height="0.5dp" android:id="#+id/line3_view_event_detail" android:background="#696C79"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="#+id/textView_event_detail_price_label"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="16dp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/textView_event_detail_description_snippet_label"
app:layout_constraintTop_toTopOf="#+id/line3_view_event_detail"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp" tools:text="Kajian akan membahas kitab Riyadush Shalihin Bab 3"
android:maxLines="2"/>
<TextView
android:text="Baca Selengkapnya"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textView_button_read_more"
app:layout_constraintTop_toBottomOf="#+id/textView_event_detail_description_snippet_label"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
android:clickable="true"
android:focusable="true" android:textColor="#2196F3"/>
<View
android:layout_width="0dp"
android:layout_height="0.5dp" android:id="#+id/line4_view_event_detail" android:background="#696C79"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="#+id/textView_button_read_more"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="16dp"/>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="0dp"
android:layout_height="180dp"
android:id="#+id/map_event_detail"
tools:context=".Activities.MainActivity"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="#+id/line4_view_event_detail"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:text="Peta Ke Lokasi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textView_button_map_more"
app:layout_constraintTop_toBottomOf="#+id/map_event_detail"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
android:clickable="true"
android:focusable="true" android:textColor="#2196F3"/>
<View
android:layout_width="0dp"
android:layout_height="0.5dp" android:id="#+id/line5_view_event_detail" android:background="#696C79"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="#+id/textView_button_map_more"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="16dp"/>
<TextView
android:text="Penyelenggara"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textView_event_detail_organizer_tag"
app:layout_constraintTop_toBottomOf="#+id/line5_view_event_detail"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"/>
<TextView
android:text="Masjid Nurul Iman"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textView_event_detail_organizer_label"
app:layout_constraintTop_toBottomOf="#+id/textView_event_detail_organizer_tag"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
I have tried to comment out all codes in my onCreateView. so this fragment will do nothing. and still, my app render slowly when showing this fragment.
but when I try to remove support map fragment from my xml, I mean this part:
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="0dp"
android:layout_height="180dp"
android:id="#+id/map_event_detail"
tools:context=".Activities.MainActivity"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="#+id/line4_view_event_detail"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"/>
it will make my app render faster. so why this is happened or is it any alternatives to solve this ?
I mean, to show a map in the constraint layout inside a scroll view ?
here is my code:
class EventDetailFragment : Fragment() {
lateinit var titleTextView: TextView
lateinit var fragmentView : View
lateinit var eventPosterImageView : ImageView
lateinit var eventBlurredPosterImageView: ImageView
lateinit var eventTitleTextView : TextView
lateinit var attendanceButton: Button
lateinit var dateTextView: TextView
lateinit var timeTextView : TextView
lateinit var venueTextView: TextView
lateinit var addressTextView: TextView
lateinit var speakerTextView: TextView
lateinit var priceTextView: TextView
lateinit var descriptionSnippetTextView: TextView
lateinit var readMoreTextViewButton : TextView
lateinit var mapMoreTextViewButton : TextView
lateinit var organizerTagTextView: TextView
lateinit var organizerLabelTextView: TextView
lateinit var mapFragment : SupportMapFragment
lateinit var googleMap: GoogleMap
lateinit var mContext : Context
lateinit var mActivity : FragmentActivity
lateinit var selectedEvent : Event
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
activity?.let { mActivity = it }
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentView = inflater.inflate(R.layout.fragment_event_detail, container, false)
// setUpViewsDeclaration()
// setUpSafeArg()
// setUpListeners()
// updateUI()
return fragmentView
}
private fun setUpViewsDeclaration() {
titleTextView = mActivity.findViewById(R.id.destination_label_text_view)
eventPosterImageView = fragmentView.findViewById(R.id.imageView_event_detail_poster)
eventBlurredPosterImageView = fragmentView.findViewById(R.id.imageView_event_detail_blurred_poster)
eventTitleTextView = fragmentView.findViewById(R.id.textView_event_detail_event_title)
attendanceButton = fragmentView.findViewById(R.id.button_search_event_search)
dateTextView = fragmentView.findViewById(R.id.textView_event_detail_date_label)
timeTextView = fragmentView.findViewById(R.id.textView_event_detail_time_label)
venueTextView = fragmentView.findViewById(R.id.textView_event_detail_venue_label)
addressTextView = fragmentView.findViewById(R.id.textView_event_detail_address_label)
speakerTextView = fragmentView.findViewById(R.id.textView_event_detail_speaker_label)
priceTextView = fragmentView.findViewById(R.id.textView_event_detail_price_label)
descriptionSnippetTextView = fragmentView.findViewById(R.id.textView_event_detail_description_snippet_label)
readMoreTextViewButton = fragmentView.findViewById(R.id.textView_button_read_more)
organizerTagTextView = fragmentView.findViewById(R.id.textView_event_detail_organizer_tag)
organizerLabelTextView = fragmentView.findViewById(R.id.textView_event_detail_organizer_label)
mapMoreTextViewButton = fragmentView.findViewById(R.id.textView_button_map_more)
}
private fun setUpSafeArg() {
arguments?.let {
val args = EventDetailFragmentArgs.fromBundle(it)
selectedEvent = args.selectedEvent
}
}
private fun setUpListeners() {
eventPosterImageView.setOnClickListener {
val photoViewNavigation = EventDetailFragmentDirections.actionGlobalPhotoViewFragment(selectedEvent.posterDownloadPath)
Navigation.findNavController(fragmentView).navigate(photoViewNavigation)
}
eventBlurredPosterImageView.setOnClickListener {
val photoViewNavigation = EventDetailFragmentDirections.actionGlobalPhotoViewFragment(selectedEvent.posterDownloadPath)
Navigation.findNavController(fragmentView).navigate(photoViewNavigation)
}
readMoreTextViewButton.setOnClickListener {
val nextDestination = EventDetailFragmentDirections.actionToDescriptionDetail(selectedEvent.description)
Navigation.findNavController(fragmentView).navigate(nextDestination)
}
mapMoreTextViewButton.setOnClickListener {
val latitudeFloat = selectedEvent.coordinate.latitude.toFloat()
val longitudeFloat = selectedEvent.coordinate.longitude.toFloat()
val mapDetailDestination = EventDetailFragmentDirections.actionToMapDetailLocation(latitudeFloat,longitudeFloat)
Navigation.findNavController(fragmentView).navigate(mapDetailDestination)
}
}
private fun updateUI() {
Picasso.get()
.load(selectedEvent.posterDownloadPath)
.into(eventPosterImageView)
Picasso.get()
.load(selectedEvent.posterDownloadPath)
.transform(BlurTransformation(mContext,25,3))
.into(eventBlurredPosterImageView)
eventTitleTextView.text = selectedEvent.title
val dateStartingDate = DateTimeService.changeDateToString("EEEE, d MMMM yyyy",selectedEvent.dateTimeStart)
val timeStartingDate = DateTimeService.changeDateToString("HH:mm",selectedEvent.dateTimeStart)
val dateEndingDate = DateTimeService.changeDateToString("EEEE, d MMMM yyyy",selectedEvent.dateTimeFinish)
val timeEndingDate = DateTimeService.changeDateToString("HH:mm zzz",selectedEvent.dateTimeFinish)
dateTextView.text = dateStartingDate
timeTextView.text = "$timeStartingDate - $timeEndingDate"
venueTextView.text = selectedEvent.venue
addressTextView.text = selectedEvent.address
speakerTextView.text = "Penceramah: ${selectedEvent.speaker}"
organizerLabelTextView.text = selectedEvent.creatorFullName
descriptionSnippetTextView.text = selectedEvent.description
setUpGoogleMap()
}
private fun setUpGoogleMap() {
mapFragment = childFragmentManager.findFragmentById(R.id.map_event_detail) as SupportMapFragment
mapFragment.getMapAsync {
googleMap = it
val eventLocation = LatLng(selectedEvent.coordinate.latitude,selectedEvent.coordinate.longitude)
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(eventLocation,16f))
googleMap.addMarker(MarkerOptions().position(eventLocation).title("Lokasi Acara")).showInfoWindow()
googleMap.setOnMapClickListener {
val latitudeFloat = selectedEvent.coordinate.latitude.toFloat()
val longitudeFloat = selectedEvent.coordinate.longitude.toFloat()
val mapDetailDestination = EventDetailFragmentDirections.actionToMapDetailLocation(latitudeFloat,longitudeFloat)
Navigation.findNavController(fragmentView).navigate(mapDetailDestination)
}
}
}
}
I would suggest the use of MapView instead of the SupportMapFragment as you would have better control over the lifecycle of the map.
I think that you're unable to replace the fragment out since SupportMapFragment integrates the Map's lifecycle with the Activity and you're unable to replace it until the Map has completed loading.
MapView will give you better control over your map as you should be able to get your fragment replaced even if the map hasn't loaded
Check out this answer ( explains the difference better than me ) :- Differences between MapView, MapFrament and SupportMapFragment
you have to much widget and element to show , i think you are facing a jank , from the official documentation
UI Rendering is the act of generating a frame from your app and displaying it on the screen. To ensure that a user's interaction with your app is smooth, your app should render frames in under 16ms to achieve 60 frames per second (why 60fps?). If your app suffers from slow UI rendering, then the system is forced to skip frames and the user will perceive stuttering in your app. We call this jank.
i advice to read carefully the documentation to get better understanding of how to make UI smooth

Constraint Set animation inside Recycler View not animating properly

I am using a constraint layout for my recycler view items. To animate (expand/collapse) them I use Constraint Set animation. The opening animation runs fine on all items. The closing animation runs fine also, but when the closing animation starts on item that is not the last all items jump up when animation starts, and not at the end of the animation.
Animation is performed on item click:
itemView.setOnClickListener {
val smallItemConstraint = ConstraintSet()
smallItemConstraint.clone(itemView.context, R.layout.day_of_week_small)
val largeItemConstraint = ConstraintSet()
largeItemConstraint.clone(itemView.context, R.layout.day_of_week)
val constraintToApply = if (isViewExpanded) smallItemConstraint else
largeItemConstraint
animateItemView(constraintToApply, itemView.dayOfWeekConstraintLayout)
if (!isViewExpanded) {
itemView.dayOfWeekWeatherIcon.visibility = View.VISIBLE
} else {
itemView.dayOfWeekWeatherIcon.visibility = View.GONE
}
isViewExpanded = !isViewExpanded
}
Where animateItemView is:
private fun animateItemView(constraintToApply: ConstraintSet,
constraintLayout: ConstraintLayout) {
TransitionManager.beginDelayedTransition(constraintLayout)
constraintToApply.applyTo(constraintLayout)
}
day_of_week.xml (expanded) layout:
<?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:id="#+id/dayOfWeekConstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/dayOfWeekWeatherIcon"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:contentDescription="#string/weather_image"
app:layout_constraintBottom_toBottomOf="#+id/dayOfWeekHumidityLabel"
app:layout_constraintEnd_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="#tools:sample/avatars" />
<TextView
android:id="#+id/dayOfWeekText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/today"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/dayOfWeekItemVerticalGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="192dp" />
<TextView
android:id="#+id/dayOfWeekCurrentTemperatureText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAllCaps="true"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekText" />
<TextView
android:id="#+id/dayOfWeekDegreeCelsiusSign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/degree_celsius"
android:textAllCaps="true"
android:textSize="24sp"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekCurrentTemperatureText"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekText" />
<TextView
android:id="#+id/dayOfWeekWeatherStateText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/weather_state_text"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekDegreeCelsiusSign" />
<TextView
android:id="#+id/dayOfWeekWindLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/wind_label"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWeatherStateText" />
<TextView
android:id="#+id/dayOfWeekHumidityLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="#string/humidityLabel"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWindLabel" />
<TextView
android:id="#+id/dayOfWeekWindDirection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekWindLabel"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWeatherStateText" />
<TextView
android:id="#+id/dayOfWeekWindSpeed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textSize="14sp"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekWindDirection"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWeatherStateText" />
<TextView
android:id="#+id/dayOfWeekWindSpeedLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/wind_speed"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekWindSpeed"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWeatherStateText" />
<TextView
android:id="#+id/dayOfWeekHumidityText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekHumidityLabel"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWindSpeedLabel" />
<TextView
android:id="#+id/dayOfWeekHumidityPercentageLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="#string/percentage_sign"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekHumidityText"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWindSpeedLabel" />
</androidx.constraintlayout.widget.ConstraintLayout>
And day_of_week_small.xml (collapsed) layout:
<?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:id="#+id/dayOfWeekConstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/dayOfWeekWeatherIcon"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:contentDescription="#string/weather_image"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="#+id/dayOfWeekHumidityLabel"
app:layout_constraintEnd_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="#tools:sample/avatars" />
<TextView
android:id="#+id/dayOfWeekText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/today"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/dayOfWeekItemVerticalGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="192dp" />
<TextView
android:id="#+id/dayOfWeekCurrentTemperatureText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAllCaps="true"
android:textSize="40sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekText" />
<TextView
android:id="#+id/dayOfWeekDegreeCelsiusSign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/degree_celsius"
android:textAllCaps="true"
android:textSize="40sp"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekCurrentTemperatureText"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekText" />
<TextView
android:id="#+id/dayOfWeekWeatherStateText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/weather_state_text"
android:textSize="24sp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekDegreeCelsiusSign" />
<TextView
android:id="#+id/dayOfWeekWindLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/wind_label"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintStart_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWeatherStateText" />
<TextView
android:id="#+id/dayOfWeekHumidityLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="#string/humidityLabel"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintStart_toStartOf="#+id/dayOfWeekItemVerticalGuideline"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWindLabel" />
<TextView
android:id="#+id/dayOfWeekWindDirection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekWindLabel"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWeatherStateText" />
<TextView
android:id="#+id/dayOfWeekWindSpeed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekWindDirection"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWeatherStateText" />
<TextView
android:id="#+id/dayOfWeekWindSpeedLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="#string/wind_speed"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekWindSpeed"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWeatherStateText" />
<TextView
android:id="#+id/dayOfWeekHumidityText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekHumidityLabel"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekWindSpeedLabel" />
<TextView
android:id="#+id/dayOfWeekHumidityPercentageLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="#string/percentage_sign"
android:textAllCaps="true"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintStart_toEndOf="#+id/dayOfWeekCurrentTemperatureText"
app:layout_constraintTop_toBottomOf="#+id/dayOfWeekCurrentTemperatureText" />
</androidx.constraintlayout.widget.ConstraintLayout>
What is the issue here and how do I fix it?
Thank you.
Animation example:
Before we get to how I was able to get everything to work, let's take a look at what is causing the behavior in your gif.
The reason why the other item views jumps up is because animations are purely visual. That is, the collapse animation doesn't actually animate the height of your item from a layout perspective, it only animates how the item is drawn. This is done for performance reasons (imagine having to re-layout all of the views 60 times a second). That is why when your item is collapsed all of the other views jump to the end layout position.
RecyclerViews are very good at animating the heights of their children and this is what we will use to solve the entire animation problem. I outline the complete solution below.
Preview GIF: https://giphy.com/gifs/SVlBnpeW3wIwNIVpVU
I was able to get ConstraintLayout + ConstrainSet + RecyclerViews working after some experimentation. I will share how I got it working.
The code
Here is a quick preview of the code.
private inner class MatchInfoAdapter (
private val context: Context
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var items = listOf<MatchItem>()
private val inflater: LayoutInflater = LayoutInflater.from(context)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder =
FullViewHolder(inflater.inflate(viewType, parent, false))
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
payloads: MutableList<Any>
) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
val item = items[position]
val h = holder as FullViewHolder
if (!item.isExpanded) {
h.collapsedConstraintSet.applyTo(h.rootView)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = items[position]
val h = holder as FullViewHolder
val isExpanded = item.isExpanded
val constraint = if (isExpanded)
h.expandedConstraintSet
else
h.collapsedConstraintSet
constraint.applyTo(h.rootView)
bindGeneralViews(h, item, isExpanded)
if (isExpanded) {
bindExpandedExtraViews(h, item)
}
h.clickView.setOnClickListener {
toggleExpanded(h)
}
}
private fun bindGeneralViews(
h: FullViewHolder,
item: MatchItem,
isExpanded: Boolean
) {
// bind views that are visible when expanded and collapsed
}
private funbindExpandedExtraViews(
h: FullViewHolder,
item: MatchItem
) {
// bind views that are only shown when the item is expanded
}
private fun toggleExpanded(
h: FullViewHolder
) {
if (h.adapterPosition< 0) return // touch event can technically fire after a view is unbound
val autoTransition = AutoTransition()
val item = items[position]
item.isExpanded = !item.isExpanded
bindGeneralViews(h, item, newIsExpanded)
if (item.isExpanded) {
bindExpandedExtraViews(h, item)
autoTransition.ordering = AutoTransition.ORDERING_TOGETHER
autoTransition.duration = ANIMATION_DURATION_MS
TransitionManager.beginDelayedTransition(h.rootView, autoTransition)
h.expandedConstraintSet.applyTo(h.rootView)
notifyItemChanged(h.adapterPosition, Unit)
} else {
autoTransition.ordering = AutoTransition.ORDERING_TOGETHER
autoTransition.duration = ANIMATION_DURATION_MS
TransitionManager.beginDelayedTransition((h.rootView.parent as ViewGroup), autoTransition)
notifyItemChanged(h.adapterPosition, Unit)
}
}
}
data class MatchItem(
...
) {
// Exclude this field from equals/hachcode by declaring it in class body
var isExpanded: Boolean = false
}
private class FullViewHolder (itemView: View) : RecyclerView.ViewHolder(itemView) {
...
val collapsedConstraintSet: ConstraintSet = ConstraintSet()
val expandedConstraintSet: ConstraintSet = ConstraintSet()
init {
collapsedConstraintSet.clone(rootView)
expandedConstraintSet.clone(rootView.context, R.layout.build_full_item)
}
}
How does it work?
The code relies heavily on notifyItemChanged(Int, Payload) and TransitionManager.beginDelayedTransition(). Let’s go over how these work first.
First, notifyItemChanged(Int, Payload) will ensure that the view holder passed to onBindViewHolder(RecyclerView.ViewHolder, Int, MutableList<Any>) is the same view holder as the view holder that is currently bound. Eg. let’s say A is the view holder currently bound to item 0. If we call notifyItemChanged(0, Unit) then we can guarantee that A will be passed to onBindViewHolder(RecyclerView.ViewHolder, Int, MutableList<Any>). In addition to this, RecyclerViews are very good at animating changes in item view height so notifyItemChanged() will notify the RecyclerView to check if the height changed and if it has to play a nice animation that will either animate other items up or down.
Second, TransitionManager.beginDelayedTransition() takes a snapshot of the current state of the view passed in. Then when ConstraintSet.applyTo() is called, the difference between the saved state and the current state is calculated and animations are applied to transition between the two, automatically.
Now that the basics are out of the way. Here is how expanding and collapsing items work.
For expanding an item:
User taps on the items.
toggleExpanded() is called.
Item’s view state is updated to expanded.
We pre-bind all of the views to the view holder so that no flickering occurs during animation and all views are fully bound.
TransitionManager.beginDelayedTransition() is called to take a snapshot of the item view state.
ConstraintSet.applyTo() is called to apply our expanded layout to the view and to animate the changes.
notifyItemChanged(h.``adapterPosition``, Unit) is called. This guarantees that when onBindViewHolder is called, we will get our fully bound view holder passed to us. In addition, it notifies the recyclerview that the height of the item has changed will let the recyclerview handle animating the height change.
For collapsing an item:
User taps on the items.
toggleExpanded() is called.
Item’s view state is updated to collapsed.
TransitionManager.beginDelayedTransition() is called to take a snapshot of the item view state.
notifyItemChanged(h.``adapterPosition``, Unit) is called. This guarantees that when onBindViewHolder is called, we will get our fully bound view holder passed to us. In addition, it notifies the recyclerview that the height of the item has changed will let the recyclerview handle animating the height change.
ConstraintSet.applyTo() is called to apply our collapsed layout to the view and to animate the changes.
Additional fun facts
Collapsing an item is actually way more complicated than meets the eye. The TransitionManager.beginDelayedTransition() call before notifyItemChanged(h.adapterPosition, Unit) is crucial. This is because the view holder passed to onBindViewHolder is always unbound due to how recyclerviews are implemented.
Why is this an issue? Well it means that if we were to call TransitionManager.beginDelayedTransition() in onBindViewHolder instead, the state it will save is that the view is unbound. When ConstraintSet.applyTo() is called, it will animate between an unbound view to a bound view and the default animation for this is to fade the view in. This is not what we want and the animation looks very ugly.

Categories

Resources