StackOverflowError when inflating custom view which contains its own views - android

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

Related

How to access sub elements of a custom FrameLayout in a library project?

I am extending a "MapView" that itself extends FrameLayout. But I can't access the ui elements I added: level_layout and *level_scroll. I have no trouble adding the code directly to my MainActivity, but I can't get it to work in a library.
<?xml version="1.0" encoding="utf-8"?>
<android.company.com.library.mapbox.MyMapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mapView">
<ScrollView
android:id="#+id/level_scroll"
android:layout_width="50dp"
android:layout_height="250dp"
android:layout_gravity="right"
android:layout_marginTop="100dp"
android:orientation="vertical">
<LinearLayout
android:id="#+id/level_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"></LinearLayout>
</ScrollView>
</android.company.com.library.mapbox.MyMapView>
In MyMapView.java I am getting a value from:
int level = R.id.level_layout;
But linear is null when trying to get the LinearLayout
LinearLayout linear = (LinearLayout) findViewById(R.id.level_layout);
I tried Adding
#Override
protected void onFinishInflate() {
super.onFinishInflate();
}
(I try to access my ui elements after this is called)
Calling the following in different places (e.g. constructors)
LayoutInflater inflater = (LayoutInflater)this.getContext().getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.nameOfTheXmlFile, null);
I don't have multiple layout files with the same name.
I can't use setContentView(...) since this is a View and not an Activity?
Official documentation to MapView here explicitly says
Note: You are advised not to add children to this view.
This means that MapView custom inner views are supported only partially, or not supported at all. I would not recommend you to use something that is badly supported since it may produce weird bugs as the one you have. Especially in MapView with its complicated touch listener and extensive inner UI - you can easily break something(for example zoom or camera swiping with your ScrollView)
Frankly I'm not sure why are you doing this - you can easily place all the views you need in ordinary FrameLayout and everything will work fine(except maybe ScrollView swipe gesture processor may disrupt MapView gestures)
So it would be wiser do to it like this
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.company.com.library.mapbox.MyMapView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mapView"/>
<FrameLayout
android:id="#+id/wrapper_layout"
android:layout_width="50dp"
android:layout_height="250dp"
android:layout_gravity="right"
android:layout_marginTop="100dp">
<ScrollView
android:id="#+id/level_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/level_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</FrameLayout>
</FrameLayout>
This way you will be able to leverage all the possibilities of MapView and FrameLayout separately. Don't forget to handle lifecycle changes for the MapView in your fragment/activity as it is told here.
You may also notice that I wrapped ScrollView with FrameLayout and gave this wrapper layout wight and height, while ScrollView has match_parent. I've done that because ScrollView with defined height and width can be a real pain in the arse and is quite bug prone, thus wrapping it in some other layout is recommended workaround for such purposes.
While it is not the exact solution to your problem, I hope this answer will help you somehow.

Reuse a rendered layout in some other layout at run time

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

Is there any way to re-fresh or re-create a custom view?

i have create a custom view class, this class contains three TextViews. I am just using this custom view in on of my Fragment layout. My layout is like below
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<com.abc.views.HeaderView
android:id="#+id/headerView"
android:visibility="visible"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</com.rfs.app.views.HeaderView>
<ScrollView
android:id="#+id/widget54"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/footerView"
android:layout_below="#+id/headerView" >
--------------------------------------
--------------------------------------
</ScrollView>
</RelativeLayout>
The problem is, HeaderView child's updating(change text/color) when i click on button in my fragment. these changes are not reflecting instantly.
all child's are textviews in HeaderView class.
Is there any way to re-fresh or re-create this custom view?
Obviously your custom view doesn't invalidates itself correctly. Please make sure you call invalidate or postInvalidate if doing work in background thread, whenever you want your custom view to call onDraw. You can share your custom view code so that we can point out the issue here.

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 . .....

Does LayoutInflater work with a layout xml file that uses includes?

Here's the rub: I have a layout that uses the include tag. The layout is simple ->
<LinearLayout>
<TextView ...>
<LinearLayout>
<include ...>
</LinearLayout>
</LinearLayout>
Now the included file is nothing more than:
<LinearLayout>
<TextView ... android:id="#+id/inner_text">
</LinearLayout>
Now, if I try to access the TextView inner_text in either the create or onStart call, it throws a NPE. I have tried using LayoutInflater to infate the included xml file and then access inner_text but to no avail - it always fails.
So, the question is : does LayoutInflater work with included xml files of the parent xml file? What I would like to do is grab the contents of the included xml file - and set the whole thing to be either visible or not visible based on preferences.
Nothing seems to allow me to grab the TextView object.
Now, when I include the xml as just a nested element in the parent file (not using the include tag) - then it accesses that LinearLayout just fine - but that defeats my purpose of trying to make the layout of the view dynamic - that is, I can change the contents of the child included layout at will - and not have to do any changes to the parent layout.
Any help, pointers, suggestions -> greatly appreciated.
As suggestion I think you can have in the main xml some Layout component left empty, them in runtime, inflate the main xml, then the included xml and add the content of the included in the space you left in the main.
But I think you example have to work, internally the Android uses the inflater to inflate resources. Can you edit and tell a bit more? Code examples can be helpfully.
well, very strange, when you inflate a view from any xml resource (if xml resources have or have not nested resources with include) the view must hold all the parsed xml. So, yes, LayoutInflater work with included xml files of the parent xml file.
i copied here the way i'm doing now...
my generic layout (a header with text, progress bar and image)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip">
<ImageView
android:id="#+id/genericHeaderLogoPin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:src="#drawable/logo_pin"/>
<TextView
android:id="#+id/genericHeaderTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="#id/genericHeaderLogoPin"
android:textSize="18dip"
android:textStyle="bold"
android:textColor="#FF0C9994"/>
<ProgressBar
android:id="#+id/genericHeaderProgressBar"
android:layout_width="25dip"
android:layout_height="25dip"
android:layout_centerVertical="true"
android:layout_toLeftOf="#id/genericHeaderLogoPin"
style="#android:style/Widget.ProgressBar.Inverse"
android:visibility="gone"/>
</RelativeLayout>
my parent layout... include the generic layout and a ListView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#drawable/background_new_search_activity_1">
<include layout="#layout/generic_header_layout" android:id="#+id/listaBusquedasHeader"/>
<ListView
android:id="#android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:cacheColorHint="#00000000"/>
</LinearLayout>
my onCreate method that inflate the parent layout mentioned above:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
requestWindowFeature(Window.FEATURE_NO_TITLE);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.lista_busquedas_activity, null);
setContentView(view);
listView = (ListView) findViewById(android.R.id.list);
listView.setOnItemClickListener(listItemClickListener);
((TextView)findViewById(R.id.genericHeaderTitle)).setText(R.string.messagesActivityTitle);
}
and that´s all, i hope this helps you.
Thanx for the help - I discovered what my problem was - I was trying to inflate the included file - not the parent container - so that's why it never found the elements. I wish the documentation would have mentioned that - logically I would think the parent xml file would load - but the included files may need inflating - guess not.
Works now thanks to your code examples you submitted.
Thanx.

Categories

Resources