BottomNavigationView layout broken, when add menu items programmatically - android

For some reason BottomNavigationView has a visual bug in layout. Does anyone know any way to fix it? The problem resolves after any button is clicked or after I minimize app and restore it.
This is how it is supposed to look:
Everything works when menu is inflated via XML.
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="56dp"
...
app:menu="#menu/bottom_navigation_4_game" />
When I added MenuItem programmatically:
navigationView.menu.clear()
navigationView.inflateMenu(R.menu.bottom_navigation4)
We may see in LayoutInspector, that there are actually 5 items, but two of them are overlayed and not seen:
The problem is probably in BottomNavigationMenuView. In LayoutInspector getWidth() returns 0. Invalidating views didn't help.

If you are trying to create dynamic BottomNavigationView with 2 different menu items set,
So instead of dynamically adding the menu item, use 2 different xml layouts (which have define 2 different app:menu property) and based on the conditions switch between them in your code then.
So, XML would look like this:
<BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="#dimen/bottom_bar_height"
app:elevation="8dp"
app:itemIconTint="?attr/nav_item_color_state_red"
app:itemTextColor="?attr/nav_item_color_state_red"
app:labelVisibilityMode="labeled"
app:menu="#menu/bottom_navigation_1" />
<BottomNavigationView
android:id="#+id/bottom_navigation_mini_player"
android:layout_width="match_parent"
android:layout_height="#dimen/bottom_bar_height"
app:elevation="8dp"
app:itemIconTint="?attr/nav_item_color_state_red"
app:itemTextColor="?attr/nav_item_color_state_red"
app:labelVisibilityMode="labeled"
app:menu="#menu/bottom_navigation_2" />

I found a weird code block in the app, that was to blame. Turns out, that TransitionManager didn't end its transitions with ConstraintLayout. This code: updateConstraints {} was called immediately after dynamically changing BottomNavigationView, hence its child views transition was interrupted, I guess.
private fun updateConstraints(f: ConstraintSet.() -> Unit) {
TransitionManager.beginDelayedTransition(root)
val set = ConstraintSet().apply { clone(root) }
set.f()
set.applyTo(root)
}

Related

Changing theme at runtime from fragment in android kotlin

I have created an activity. The activity's layout contains FragmentContainerView element.
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/main_navigation_graph" />
I am showing a fragment into this container using the following code,
val navHostFragment = activity?.supportFragmentManager?.findFragmentById(R.id.nav_host_container) as NavHostFragment
val navController = navHostFragment.navController
navController.navigate(destinationID)
Here i am showing a fragment named as HomeFragment. The home fragment layout contains a linear layout. 50% of space is given to ViewPager2 and 50% is given to a group of 4 buttons, both these layouts are separated vertically. Currently there are multiple items in my viewpager2 and i am looking to change the Ui of these 4 buttons according to the item in the viewpager2. The meaning of changing Ui is to change/switch the entire theme of the fragment or activity at runtime. I have been checking solutions on stack where answers are given to change the theme using styles in OnCreateView before super is called. However it is quite complicated to change the style as i have only one fragment loaded in ContainerView. Hence my question is, can we apply a style to the layout and just change the style of the entire layout on runtime easily or is there any better solution to this ?

Fragment Button is found by Activity's findViewById instead of Fragment's inflated view.findViewById

I'm seeing this strange behavior and couldn't find anything similar to this.
So I have a parent Activity and inside is a Fragment, which I'm including in parent via include element and then in parent's onCreate, create Fragment and replace it with this include layout (Tell me if this is a right way? I was using FrameLayout but then switched to include and defined an id to it).
Activity
<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:fitsSystemWindows="true"
tools:context="com.sourcey.materiallogindemo.CustomerDetailActivity"
tools:ignore="MergeRootFrame">
<com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.appbar.MaterialToolbar />
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="#id/app_bar"
app:layout_constraintBottom_toTopOf="#id/layout_bottom_bar"
layout="#layout/fragment_customer_detail" />
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomappbar.BottomAppBar>
</com.google.android.material.bottomappbar.BottomAppBar>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.sourcey.materiallogindemo.CustomerDetailFragment"
android:layout_height="match_parent"
android:layout_width="match_parent">
<!-- THIS IS THE CULPRIT -->
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_update_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/sku_list"
app:layoutManager="LinearLayoutManager"
tools:context="com.sourcey.materiallogindemo.CustomerDetailFragment"
tools:listitem="#layout/fragment_s_k_u_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment is inflated correctly but when I do this inside onCreateView
rootView.btn_update_position.setOnClickListener {
// ... log something
}
and press the Button, it doesn't do anything? Even though most findings were led to this suggestion that I should inflate the view and then set onClickListener.
I also tried doing these
rootView.findViewById<MaterialButton>(R.id.btn_update_position).setOnClickListener {
// ... log something
}
and
val button = rootView.findViewById<MaterialButton>(R.id.btn_update_position)
button.setOnClickListener {
// ... log something
}
but none of them works.
I also tried above approaches in onViewCreated to see if maybe I was not getting the reference but no errors were thrown and no reaction was coming.
Only thing that works is this
activity?.findViewById<MaterialButton>(R.id.btn_update_position)
?.setOnClickListener {
// ... log something
}
I'm trying to understand why this happens? Could this be the issue of using include the Fragment?
NOTE I'm not a pro in android just do hobby work in it so don't know very deeply about it.
EDIT As you can see I have a RecyclerView in Fragment layout, I'm inflating the layout and then setting its adapter items which seems to work fine opposed to button.
rootView.sku_list.adapter = Adapter()
I'm bit confused about what you want to do here
First,<include> doesn't create new view, it just include the xml into the parent xml file so basically it still on activity and you need activity to findViewById
Second, about your question what different between FrameLayout and <include>.
With <include> like i said above, it just add xml file to the parent file, the main usage is for re-use layout (you can include it anywhere) .
With FrameLayout, from official doc : "FrameLayout is designed to block out an area on the screen to display a single item". E.g : you want your layout have a header and footer for all screen, only the middle part change so place a frame layout at middle then load different view for each screen, because that flexibility frame layout usually use for display fragment (you can google how to use frame layout for more details)

RecyclerView not updating the view properly with ConstraintLayout Group Visibility

Problem:
It seems when I have a specific combination of views and setVisibility between Visible and Gone the RecyclerView does not update properly after initial load. I have a RelativeLayout->ConstraintLayout->Constraint Group(with visibility dynamically set). The view within the constraint group is not updating properly.
Example Use Case of Problem:
So the linked code below it will show a search view at the top. The initial state of empty shows the view properly(With the search icon showing). Then if you type "T" then the search icon will disappear(it shouldn't). So if you either delete the T or type the next letter "E" then it shows again. Also if you delete all search text and type T again it will show.
View Code:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.example.davidcorrado.myapplication.PlayerVM" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="#+id/playerCell_main_layout"
android:layout_width="match_parent"
android:layout_height="84dp"
android:layout_alignParentTop="true">
<android.support.constraint.Group
android:id="#+id/playerCell_status_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="#{vm.showingStatusIcons}"
app:constraint_referenced_ids="playerCell_lineup_status_image" />
<ImageView
android:id="#+id/playerCell_lineup_status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="#drawable/ic_search" />
</android.support.constraint.ConstraintLayout>
</RelativeLayout>
</layout>
Quirks that might help:
1) If I move the visibility line to the relativeLayout or the ConstraintLayout the things seem to work
2) If I remove the RelativeLayout view things seem to work.
See the below for the full code example:
https://github.com/DavidCorrado/LayoutIssue
As far as I can tell, the reason the search icon disappears is due to the call to ObservableList.clear() then ObservableList.addAll(*)
So the disappear happens on the clear, then the reappear happens on the addAll.
As the callbacks in the adapter kick in on both calls, my hunch is it animates the view to disappear, then animates to show when the addAll is triggered.
I have verified this by not actioning a 'List.clear()' and instead in your textChange listener simply adding more views to the list.
I'm not sure how you would clear and add items to the list without animating the clear, perhaps there are some settings you can toggle in the RecyclerAdapter to ignore the remove of Views or not animate the entry of Views?
My adjusted code in your callback for class PlayersListVM
class PlayersListVM {
val filteredVms: ObservableList<ViewModel> = ObservableArrayList<ViewModel>()
val showing = PlayerVM(isShowing = true)
val missing = PlayerVM()
init {
filteredVms.add(showing)
}
fun onQueryChanged(newText: String) {
if (filteredVms.size % 2 == 1) filteredVms.add(missing)
else filteredVms.add(showing)
}
}

Exclude certain elements from being animated in android layout transitions

I have a question regarding the android layout transition framework. In particular i want to achieve an effect that a certain part of an layout slides down or up depending on the visibility of another view(s).
Imagine the following layout. (And please overlook the nested LinearLayouts here ;) )
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="#+id/changingView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
<View
android:id="#+id/changingView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
</LinearLayout>
<LinearLayout
android:id="#+id/movingView"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="false"/>
</LinearLayout>
Now what i want to achieve is that when changingView1 and changingView2 change their visibility that movingView slides up or down.
By enabling the LayoutTransition.CHANGING for the parent layout the sliding part works fine so. But this has the side effect that the movingView will also be animated when there are being items added or removed because this layout changes its bounds. And here lies my problem because this results in a very strange looking animation.
So back to my question. Is there a way to keep the sliding animation without animating layout bound changes on the movingView?
Disabling layoutTransitions on the movingView obviously does not help because this only effects the animations of the child views. I also tried playing around with enabling and disabling different LayoutTransitions on the parent layout but so far without the desired effect.
If i can add more details please let me know otherwise i hope someone can help me out.
Thanks in advance!
I know it's late but hope it can help someone. Since android:animateLayoutChanges is the property of the direct parent, you can wrap your View/Layout in a FrameLayout (or some other ViewGroup) and set android:animateLayoutChanges="false" on the new parent.
To avoid unwanted animations you can remove the animate layout changes by code when needed, something like this:
//removing the animate layout changes to prevent the default animation for the newly added items
parentLayout.setLayoutTransition(null);
/* do some logic to add the new views */
//add the animate layout changes back so the over changes will be still animated
new Handler().post(() -> {parentLayout.setLayoutTransition(new LayoutTransition());});

How to bring view in front of everything?

I have activity and a lot of widgets on it, some of them have animations and because of the animations some of the widgets are moving (translating) one over another. For example the text view is moving over some buttons . . .
Now the thing is I want the buttons to be always on the front. And when the textview is moving I want to move behind the buttons.
I can not achieve this I tried everything I know, and "bringToFront()" definitelly doesn't work.
note I do not want to control the z-order by the order of placing element to layout cause I simply can't :), the layout is complex and I can not place all the buttons at the begging of the layout
You can call bringToFront() on the view you want to get in the front
This is an example:
yourView.bringToFront();
With this code in xml
android:translationZ="90dp"
I've been looking through stack overflow to find a good answer and when i couldn't find one i went looking through the docs.
no one seems to have stumbled on this simple answer yet:
ViewCompat.setTranslationZ(view, translationZ);
default translation z is 0.0
An even simpler solution is to edit the XML of the activity. Use
android:translationZ=""
bringToFront() is the right way, but, NOTE that you must call bringToFront() and invalidate() method on highest-level view (under your root view), for e.g.:
Your view's hierarchy is:
-RelativeLayout
|--LinearLayout1
|------Button1
|------Button2
|------Button3
|--ImageView
|--LinearLayout2
|------Button4
|------Button5
|------Button6
So, when you animate back your buttons (1->6), your buttons will under (below) the ImageView. To bring it over (above) the ImageView you must call bringToFront() and invalidate() method on your LinearLayouts. Then it will work :)
**NOTE: Remember to set android:clipChildren="false" for your root layout or animate-view's gradparent_layout. Let's take a look at my real code:
.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:hw="http://schemas.android.com/apk/res-auto"
android:id="#+id/layout_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/common_theme_color"
android:orientation="vertical" >
<com.binh.helloworld.customviews.HWActionBar
android:id="#+id/action_bar"
android:layout_width="match_parent"
android:layout_height="#dimen/dimen_actionbar_height"
android:layout_alignParentTop="true"
hw:titleText="#string/app_name" >
</com.binh.helloworld.customviews.HWActionBar>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/action_bar"
android:clipChildren="false" >
<LinearLayout
android:id="#+id/layout_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:gravity="center_horizontal"
android:orientation="horizontal" >
</LinearLayout>
<ImageView
android:id="#+id/imgv_main"
android:layout_width="#dimen/common_imgv_height"
android:layout_height="#dimen/common_imgv_height"
android:layout_centerInParent="true"
android:contentDescription="#string/app_name"
android:src="#drawable/ic_launcher" />
<LinearLayout
android:id="#+id/layout_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center_horizontal"
android:orientation="horizontal" >
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
Some code in .java
private LinearLayout layoutTop, layoutBottom;
...
layoutTop = (LinearLayout) rootView.findViewById(R.id.layout_top);
layoutBottom = (LinearLayout) rootView.findViewById(R.id.layout_bottom);
...
//when animate back
//dragedView is my layoutTop's child view (i added programmatically) (like buttons in above example)
dragedView.setVisibility(View.GONE);
layoutTop.bringToFront();
layoutTop.invalidate();
dragedView.startAnimation(animation); // TranslateAnimation
dragedView.setVisibility(View.VISIBLE);
GLuck!
Try FrameLayout, it gives you the possibility to put views one above another. You can create two LinearLayouts: one with the background views, and one with foreground views, and combine them using the FrameLayout. Hope this helps.
If you are using ConstraintLayout, just put the element after the other elements to make it on front than the others
i have faced the same problem.
the following solution have worked for me.
FrameLayout glFrame=(FrameLayout) findViewById(R.id.animatedView);
glFrame.addView(yourView);
glFrame.bringToFront();
glFrame.invalidate();
2nd solution is by using xml adding this attribute to the view xml
android:translationZ=""
You can try to use the bringChildToFront, you can check if this documentation is helpful in the Android Developers page.
There can be another way which saves the day. Just init a new Dialog with desired layout and just show it. I need it for showing a loadingView over a DialogFragment and this was the only way I succeed.
Dialog topDialog = new Dialog(this, android.R.style.Theme_Translucent_NoTitleBar);
topDialog.setContentView(R.layout.dialog_top);
topDialog.show();
bringToFront() might not work in some cases like mine. But content of dialog_top layout must override anything on the ui layer. But anyway, this is an ugly workaround.
You can use BindingAdapter like this:
#BindingAdapter("bringToFront")
public static void bringToFront(View view, Boolean flag) {
if (flag) {
view.bringToFront();
}
}
<ImageView
...
app:bringToFront="#{true}"/>
The order of the overlapping views really depends of 4 things:
The attribute android:elevation which is measured in dp/sp
The attribute android:translationZ which is also measured in dp/sp.
In Constraint Layout, the order in which you put the views in your Component Tree is also the order to be shown.
The programmatically order that you set through methods like view.bringToFront() in your kotlin/java code.
The numerals 1 and 2 compite with each other and take preference over the points 3 and 4: if you set elevation="4dp" for View 1 and translationZ="2dp" for View 2, View 1 will always be on top regardless of the numerals 3 and 4.
Thanks to Stack user over this explanation, I've got this working even on Android 4.1.1
((View)myView.getParent()).requestLayout();
myView.bringToFront();
On my dynamic use, for example, I did
public void onMyClick(View v)
{
((View)v.getParent()).requestLayout();
v.bringToFront();
}
And Bamm !
You can use elevation attribute if your minimum api level is 21. And you can reorder view to the bottom of other views to bring it to front. But if elevation of other views is higher, they will be on top of your view.
If you are using a LinearLayout you should call myView.bringToFront() and after you should call parentView.requestLayout() and parentView.invalidate() to force the parent to redraw with the new child order.
Arrange them in the order you wants to show. Suppose, you wanna show view 1 on top of view 2. Then write view 2 code then write view 1 code. If you cant does this ordering, then call bringToFront() to the root view of the layout you wants to bring in front.
Try to use app:srcCompat instead of android:src
You need to use framelayout. And the better way to do this is to make the view invisible when thay are not require. Also you need to set the position for each and every view,So that they will move according to there corresponding position
You can set visibility to false of other views.
view1.setVisibility(View.GONE);
view2.setVisibility(View.GONE);
...
or
view1.setVisibility(View.INVISIBLE);
view2.setVisibility(View.INVISIBLE);
...
and set
viewN.setVisibility(View.VISIBLE);

Categories

Resources