Reuse a rendered layout in some other layout at run time - android

I am using a RecylerView inside a layout l1.xml. I am including this l1.xml inside l2.xml using include tag.
I update this RecyclerView after an api call but l2.xml is not showing the updated RecyclerView.
Is there a way to forcibly ask the parent to refresh?
invalidate(), refreshDrawableState(); on the parent layout didn't help?
Is there a smarter way to use a rendered layout in multiple places?
l1.xml
...
...
<LinearLayout
android:id="#+id/feed"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:background="#color/light_primary_background">
<include layout="#layout/events_list"/>
</LinearLayout>
...
...
events_list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/events_recycler_view"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
I update the events_recycler_view after an API call and the events_list.xml is updated but the include in l1.xml is not updated

Yes you can always use LayoutInflater to inflate a view, but the view must have the ids and type matching the id and type defined in your java code.
Check out this link for how to use layout inflater
http://www.programcreek.com/java-api-examples/android.view.LayoutInflater

Related

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)

StackOverflowError when inflating custom view which contains its own views

I am having a Fragment, where I inflate "fragment_board.xml":
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<view
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.app.BoardView"
android:id="#+id/view_board"/>
</FrameLayout>
As you can see, fragment_board contains a custom view "BoardView", from where I want to load the following "view_board.xml":
<com.app.BoardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:fadingEdge="none"
android:requiresFadingEdge="none"
android:overScrollMode="always"
android:id="#+id/board_scrollview_vertical">
<android.widget.HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none"
android:fadingEdge="none"
android:requiresFadingEdge="none"
android:overScrollMode="always"
android:id="#+id/board_scrollview_horizontal"
android:foregroundGravity="left">
</android.widget.HorizontalScrollView>
</com.app.BoardView>
My custom view contains two scroll views (I use it for panning), and I want to be able to re-use it in other layouts. The BoardView extends the outer (vertical) scroll view like this:
public class BoardView extends ScrollView
When I use it stand-alone, it inflates fine and I can findViewById() both scroll views in the inflated layout. But when I use it in a layout tree (such as a fragment), I run into problems.
In the fragment, I inflate the layout tree like this:
View view = inflater.inflate(R.layout.fragment_board, container, false)
From the fragment, I can findViewById() the outer (vertical) scroll view, but findViewById() returns null for the inner (horizontal) scroll view.
In BoardView, I inflate like this:
View view = inflate(getContext(), R.layout.view_board, this);
As said, I can find both scroll views fine when inflated by itself, but when inflated as part of the fragment, I get StackOverflowError.
It is clear why: I inflate in the fragment first, and then I inflate the same XML in the view again, which triggers another inflation of the view and so on. I get a cyclic reference here. Problem I can't figure out how I can add the inner (horizontal) scroll view to the already existing layout. I think I need to merge somehow or inflate layouts manually, but I can't figure out how.
Here are some refs (which didn't help in my case):
Android - Writing a custom (compound) component
How to inflate XML-Layout-File correctly inside Custom ViewGroup?
Create a custom View by inflating a layout?
InvocationTargetException on inflating an xml - android
Android: StackOverFlowError with InvocationTargetException when inflating layout
Can anybody suggest what I should do. If possible, I'd want BoardView to work as a generic component, which I can plug into a layout tree where needed.
As Android [according to one of the refs above] does not officially support composite components, would my best option be to drop XML layouts for the inner views and add them in code?
Please check if something like this fixes the issue:
fragment_board.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.app.BoardView
android:id="#+id/view_board"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
view_board.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:fadingEdge="none"
android:requiresFadingEdge="none"
android:overScrollMode="always"
android:id="#+id/board_scrollview_vertical">
<android.widget.HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none"
android:fadingEdge="none"
android:requiresFadingEdge="none"
android:overScrollMode="always"
android:id="#+id/board_scrollview_horizontal"
android:foregroundGravity="left">
</android.widget.HorizontalScrollView>
</merge>
More information on using MERGE and INCLUDE

How does <merge> tag work internally

I am trying to understand how does <merge> tag internally work. I have studied some examples using View Hierarchy tool. So I understand basic using and how does it work on higher level, but I wanna to study more about this tag and inflating views in general.
So let's consider some simple layouts
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/bg"
android:gravity="center_horizontal">
<include layout="#layout/titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/some_text"
android:text="#string/hello"
android:padding="10dp" />
</RelativeLayout>
And of course titlebar.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/chrome" />
</merge>
In this case we have following result.
Let's change our titlebar.xml
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/some_text"
android:src="#drawable/chrome" />
The result is really forthcoming.
Let's do more changes in our activity_main.xml file.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/bg"
android:gravity="center_horizontal">
<include layout="#layout/titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/bug"
android:text="#string/hello"
android:padding="10dp" />
</LinearLayout>
And in titlebar.xml I left everything the same.
So in this case we have some weird things
1. Now our root layout is LinearLayout. We have specified nonexistent attribute android:layout_below="#+id/some_text" in titlebar file
In the same line we have another issue #+id/some_text doesn't exist anymore, now we have #+id/bug.
Let's look at the result
Here I have some questions :
We are can use any attribute (for any layout) in merged file,but if the viewgroup(layout) where this part is included (in our case activity_main.xml) doesn't have such attributes, what how this situation is solved, just ignored as we can see in result ?
We have hardcoded id of view above imageview,it works if id is exists, but if it doesn't , as we can see in result it is also just being ignored
So I have already written a lot.
To sum up, where works of XML parser exactly with merged and include tags is described, also it will be good to know where sources of XML parser is located, to look through it.
I will be grateful for everyone who read this line and can suggest something or give advice.
Thx in advance.
If an <include> tag includes both layout_width and layout_height, it will override all of the root view's (of the file it is including) layout parameters.
A merge is a way to avoid an additional depth in the view hierarchy - an XML layout file must only have 1 root - so it must either have a single View, a ViewGroup which can include additional Views or a <merge>. When you're including a layout with multiple Views inside a ViewGroup, you may be adding extra unneeded complexity to your hierarchy - for example, 2 vertically orientated LinearLayouts may not be required. The merge allows you to remove the extra ViewGroup, and merge its Views into the ViewGroup where it is included.
Including a layout with a merge with a single View is the same as just including a layout with the single View directly, except that with the merge there isn't a 'root view' of the layout, so the include will not override its layout parameters (I believe).
When a View or ViewGroup is inflated into a ViewGroup which doesn't support the layout parameters that the child specifies, the parameters are just dropped. This occurs when including or inflating (using a layout inflater).
If a child View or ViewGroup is added to a ViewGroup (using addView), it is possible that the child already has layout parameters assigned, and if they aren't compatible with the ViewGroup it is being added to, it may result in a class cast exception later on during measuring / layout.
Your second question is to do with RelativeLayouts layout rules, and its behaviour can be defined using android:layout_alignWithParentIfMissing

Using the TwoLineListItem component

I have to create a list whose list items have 2 lines of text. I started building a custom list item, but then I discovered the TwoLineListItem component. I wrote this code:
pageFilterResultView=new TwoLineListItem(containerActivity);
pageFilterResultView.getText1().setText("Test");
However, getText1 returns null, and the second line throws a NullPointerException. So I thought I need to use an inflated layout instead of a constructor. The TwoLineListItem documentation specifies I can use the android.R.layout.two_line_list_item resource for the layout, so I changed the code to:
LayoutInflater inflater=(LayoutInflater)containerActivity.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
pageFilterResultView=(TwoLineListItem)inflater.inflate(android.R.
layout.two_line_list_item,null);
pageFilterResultView.getText1().setText("Test");
However, this throws a ClassCastException because the layout is actually a LinearLayout. TwoLineListItem inherits from RelativeLayout, so I can't even cast the layout to a higher class in the hierarchy.
So the question is: How do I use TwoLineListItem correctly? Do I have to create my own custom layout for it? If so, what's the point of this component if I still have to do all the work of creating a list item by myself?
How do I use TwoLineListItem correctly?
The TwoLineListItem widget is a facade over two TextViews that have to be provided by you. To use the TwoLineListItem in a ListView's row you'll need a row layout where you have the TwoLineListItem widget with two(at least) TextView children with specific ids(android.R.id.text1 and android.R.id.text2). Something like this:
<?xml version="1.0" encoding="utf-8"?>
<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#android:id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#android:id/text1"/>
</TwoLineListItem>
Then you can use it in the getView() method like you did:
pageFilterResultView=(TwoLineListItem)inflater.inflate(R.layout.the_layout_file_above,null);
pageFilterResultView.getText1().setText("Test");
Of course you have the possibility of using an included layout file as the child of the TwoLineListItem(as long as you have the two TextViews with the required ids):
<?xml version="1.0" encoding="utf-8"?>
<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- the android version of the two line layout -->
<include layout="#android:layout/two_line_list_item" />
</TwoLineListItem>
but this just increases the layout depth and should be avoided.
If so, what's the point of this component if I still have to do all
the work of creating a list item by myself?
Judging by the fact that you can't use this widget programmatically, I don't see the need for this component either.
put your layout code inside try catch block with ClassCastException it will works fine my Friend . .....

How to specify id when uses include in layout xml file

In my layout xml file, I have included other layout xml file (each
with a different android id).
<include layout="#layout/view_contact_name" android:id="+id/test1"/>
<include layout="#layout/view_contact_name" android:id="+id/test2"/>
But when I run it in the emulator, and start Hierarchy Viewer, each of
the layout still shows 'NO_ID', and in my code, I have
findViewById(R.id.test1) and findViewById(R.id.test2) both returns null.
Can anyone please help me with my problem ?
Specify the ID in the <include>
<include layout="#layout/test" android:id="#+id/test1" />
Then use two findViewById to access fields in the layout
View test1View = findViewById(R.id.test1);
TextView test1TextView = (TextView) test1View.findViewById(R.id.text);
Using that approach, you can access any field in any include you have.
I found out, that if you are using <merge> tag in your include layout, then the ID of include transfers to the merge tag which is not real view.
So either remove merge, or replace it with some layout.
Tor Norbye wrote:
The <include> tag is not a real view, so findByView will not find it. The #id attribute (and any other attributes you've set on the include tag) gets applied on the root tag of the included layout instead. So your activity.getView(R.id.included1) should in fact be the <TextView> itself.
Romain Guy indicates that you can override the ID of an included layout by putting an android:id attribute inside the <include> tag.
<include android:id="#+id/cell1" layout="#layout/workspace_screen" />
I think the top answer misses the most important point and might mislead people into thinking the <include/> tag creates a View that holds the include contents.
The key point is that include's id is passed to the root view of the include's layout file.
Meaning that this:
// activity_main.xml
<include layout="#layout/somelayout" android:id="#+id/someid"/>
// somelayout.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Becomes this:
// activity_main.xml
<ImageView
android:id="#+id/someid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
yes is like this, but careful when the layout inserted in include field is a custom one and you want to access that root layout. That layout in this case #layout/test test, is actually returned in first line.
test test1View = (test)findViewById(R.id.test1);
you must set id each include tag
included child element set a
new id. if you look how to generate new id, look at this entry:
https://stackoverflow.com/a/15442898/1136117
Problem is we try to use id which is not declared in current layout file.
Instead of declaring again, id can be simply referred using #+id/. If you refactor original id name through Android Studio it does refactor in included layout as well.
<include layout="#layout/toolbar"/>
<TextView
android:id="#+id/txt_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
**android:layout_below="#+id/toolbar"**
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"/>
In a case of using <RecyclerView> find the id of <include> by using an instance of inflated view or else it will return null.
public class ViewHolder extends RecyclerView.ViewHolder {
private mTextView;
public ViewHolder(View view) {
super(view);
View include_1 = view.findViewById(R.id.include_1);
mTextView = (TextView) include_1.findViewById(R.id.text_id);
}
}
If you have set id to either root tag of included layout then you can use that id
or you can set id to included layout.
But you can not set id to both it may throw exception.
<include layout="#layout/view_contact_name" android:id="+id/test1"/>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
....
</LinearLayout>
Or
<include layout="#layout/view_contact_name"/>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="#+id/llBottomMainView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
....
</LinearLayout>
When talking about include you either have an id on the root view inside the included layout file or on the include line itself and not on both. For example:
<include layout="#layout/layout1" android:id="#+id/layout1"/>
Layout 1 file
<RelativeLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/layout2">
</RelativeLayout>
The above example is wrong because technically you have two id's declared for the same layout. So what you have to do is pick which element will have the id.
To specify the id when you are including a xml file is like setting it to any xml element
Example:
*list_layout.xml*
`<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvNames"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>`
*activity_main.xml*
`<RelativeLayout
tools:context=".MainActivity">
<include
layout="#layout/list_layout"
android:id="#+id/myList" />
</RelativeLayout>`
Now if you want to get that to use in .kt file, just use normally findViewById
Exemplo
*MainActivity.kt*
`val myList: RecycleView = findViewById(R.id.myList)`
Wow, I can't believe this question doesn't have the right answer yet. It's simple tags suck. You can only change things that start with android:layout_ which android:id doesn't match. So the answer is you can't. Sorry. What you can do instead is create a class that will be a ViewGroup which will inflate the included views inside, then add that as a tag in your layout, but that's about it.

Categories

Resources