I want to implement custom ViewGroup in my case derived from FrameLayout but I want all child views added from xml to be added not directly into this view but in FrameLayout contained in this custom ViewGroup.
Let me show example to make it clear.
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="#+id/frame_layout_child_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="#+id/frame_layout_top"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</merge>
And I want to redirect adding all child view to FrameLayout with id frame_layout_child_container.
So of course I overrode methods addView() like this
#Override
public void addView(View child) {
this.mFrameLayoutChildViewsContainer.addView(child);
}
But for sure this doesn't work because for this time mFrameLayoutChildViewsContainer is not added to the root custom view.
My idea is always keep some view on on the top in this container frame_layout_top and all child views added into custom component should go to frame_layout_child_container
Example of using custom view
<CustomFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</CustomFrameLayout>
So in this case TextView should be added to the frame_layout_child_container
Is it possible to delegate adding all views into child ViewGroup like I described.
I have other ideas like using bringToFront() method every time view is added to keep them in correct z-axis order or for example when view is added, save it to array and than after inflating custom view add all views to this child FrameLayout
Suggest what to do in this case in order not to hit performance with reinflating all layout every time new view is added, if it is possible to implement in other way.
Views inflated from a layout - like your example TextView - are not added to their parent ViewGroup with addView(View child), which is why overriding just that method didn't work for you. You want to override addView(View child, int index, ViewGroup.LayoutParams params), which all of the other addView() overloads end up calling.
In that method, check if the child being added is one of your two special FrameLayouts. If it is, let the super class handle the add. Otherwise, add the child to your container FrameLayout.
public class CustomFrameLayout extends FrameLayout {
private final FrameLayout topLayout;
private final FrameLayout containerLayout;
...
public CustomFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.custom, this, true);
topLayout = (FrameLayout) findViewById(R.id.frame_layout_top);
containerLayout = (FrameLayout) findViewById(R.id.frame_layout_child_container);
}
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
final int id = child.getId();
if (id == R.id.frame_layout_top || id == R.id.frame_layout_child_container) {
super.addView(child, index, params);
}
else {
containerLayout.addView(child, index, params);
}
}
}
Related
I have a recycler view and a relative layout below recycler view. My relative layout consists of three text views.My recycler view consists of three text views and a button. My problem is recycler view is scrolling separately and textviews in relative layout are fixed. But I want both to be scrolled, which means while scrolling the screen scroll should be done for both recycler view and relative layout but not seperately. While scrolling my relative layout should be attached to the end of recycler view. I have searched a lot for doing that but there is no results for my search. So, ended up here please anybody help me out.
You have 2 options, first one (the better one) is to create a footer ViewHolder and add it to RecyclerView as a last item in adapter.
Or you can simply wrap your views in vertical LinearLayout and then wrap it in NestedScrollView like this:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
In order to get your RelativeLayout to scroll with the content of your RecyclerView, you'd need to add the RelativeLayout with static content to the end of the list your Adapter iterates. You'd then override getItemViewType in the adapter and return one type ID for the data in your RecyclerView and another for the footer RelativeLayout. Then in onCreateViewHolder you'd use the view type to inflate the right kind of view (one that binds your data or another that displays your RelativeLayout).
This process can be pretty labor intensive. You might also consider using a library like Epoxy to help create a footer view in your RecyclerView.
You can add view with text views in different layout and add to your recycler as last element. Then check posotion in getItemViewType and if it last return footer type inside RecyclerAdapter.
private static final int FOOTER = 1;
private static final int CHILD = 2;
// inflates the row layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == CHILD){
View view = mInflater.inflate(R.layout.list_item, parent, false);
return new ViewHolder(view);
}
else if(viewType == FOOTER ){
View view = mInflater.inflate(R.layout.relative, parent, false);
return new ViewHolder(view);
}
return null;
}
#Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
return FOOTER;
} else {
return CHILD;
}
}
If I create two classes, a Parent class which extends FrameLayout, and a Child class that extends View, and then use XML to initialise them. Is it possible to get child elements from the Parent class constructor? If i use getChildAt() , I always get null, since the activity is still not created.
<com.example.Parent
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_centerInParent="true">
<com.example.Child
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.Child
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.example.Parent>
Parent and Child classes
public class Parent extends FrameLayout {
public Parent(#NonNull Context context) {
super(context);
this.getChildAt(0); // return null
}
...
}
public class Child extends View {
...
}
Since you are calling getChildAt at the construction of your FrameLayout you are getting null values (your children are still not fully attached to the view). One solution would be to overwrite onLayout() or onMeasure() to get your childs.
Before inflating you can not get child view form Parent.
But you can do it dynamically but is also work in same way like , you have to add views manually to Parent view.
And then you can get child views form Parent View.
I need to create a layout which consists of a list of rectangles of different width and color. So I decided to create a custom view, which draw a rectangle and create a subclass of CursorAdapter, which will fill the ListView with my rectangles, setting their size and color with the data from Cursor.
I tried to create view in newView() method and add it to root, but it's not working.
Do I need to create some layout and inflate it in newView()? And how should I set the size and color of rectangles?
I read this answer and it has nothing in common with my question. Please read the question before you mark it as answered.
UPDATE
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
//View is created programmatically
//Set color depending on context type.
View view = LayoutInflater.from(context).inflate(R.layout.record_item_layout, parent, false);
Log.i(LOG_TAG, "(newView)new view have been created");
return view;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
Log.i(LOG_TAG, "(bindView) binding to out view");
int colorId = cursor.getInt(cursor.getColumnIndex(TrackerContract.UserRecordsEntry.COLUMN_CONTEXT_ID));
RecordView recordView = (RecordView) view;
//These layout params we need to set height to MAX, and width depending on record duration.
recordView.setLayoutParams(new ViewGroup.LayoutParams(200, ViewGroup.LayoutParams.MATCH_PARENT));
recordView.setColorId(colorId);
recordView.invalidate();
//do something?
}
<?xml version="1.0" encoding="utf-8"?>
<mypackage.RecordView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="200dp"
android:layout_height="match_parent"
android:id="#+id/record_view"
custom:colorId="5"/>
Kind of solved the problem myself. The easiest way inflate the custom view in newView() method, is to create a layout containing that item. In my case
<?xml version="1.0" encoding="utf-8"?>
<mypackage.RecordView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="200dp"
android:layout_height="match_parent"
android:id="#+id/record_view"
custom:colorId="5"/>
After that i could change the view size and color in bindView() method.
I'm having trouble rendering a custom view insider recycler view widget. Traditionally we inflate the view inside RecylerView.Adapter like
public RowLayoutViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.sample_view, viewGroup, false);
RowLayoutViewHolder rowLayoutViewHolder = new RowLayoutViewHolder(view);
return rowLayoutViewHolder;
}
This works fine and we can bind the data to the view inside onBindViewHolder(...) method. But when I try to create subclass of ViewGroup and use that like this, I get a blank ("black") screen.
public ImageGalleryAdapter.RowLayoutViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
SampleView view = new SampleView(context);
RowLayoutViewHolder rowLayoutViewHolder = new RowLayoutViewHolder(view);
return rowLayoutViewHolder;
}
Here's my SampleView class -
public class SampleView extends ViewGroup {
public SampleView(Context context) {
super(context);
initView();
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(l, t, l + 600, t + 200);
}
}
private void initView() {
inflate(getContext(), R.layout.sample_view, this);
TextView textView = (TextView) findViewById(R.id.sampleTextView);
textView.setTextColor(Color.WHITE);
textView.setText("Hello from textview");
}
}
And here is the layout -
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/sampleTextView"
android:text="Sample TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Looking at the source code it seems like inflating the layout behaves exactly same as creating a view, except the inflation process adds a suitable LayoutParameter to the child view based on the rootView. I've tried adding that manually but without any result.
Any help will be appreciated.
It's pretty simple. If you see the way you add view by inflating it, you would realize that you are adding it to the parent (ViewGroup) but not attaching it. In this process, default LayoutParams are generated and set to your view. (Check LayoutInflater source)
SampleView view = new SampleView(context);
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
This is how it should be. Even the following seems to be working.
viewGroup.add(view, new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
I've successfully solved the issue in a roundabout way that might help
In the custom View, say SampleView, in whatever method you wish to modify the layout (I guess here onLayout()), register
getViewTreeObserver().addOnGlobalLayoutListener(new LayoutViewTreeObserver())
in the callback for LayoutViewTreeObserver:
#Override
public void onGlobalLayout() {
// Do layout stuff here
}
Also, as a side note, don't forget to remove the GlobalOnLayoutListener before the modifications so the callback doesn't get anymore necessary calls (in practice, I've had to call getViewTreeObserver() instead of keeping a reference to the observer as a lot of times a reference stops being "alive" and throws an error when I try to unregister it).
While the above worked for me, I realized that it is far from ideal since it relies on the fact that the custom view generally obtain the LayoutParams of the container class, which if used elsewhere will not be RecyclerView.LayoutParams, thus causing an error. However, I have found that using an XML layout with just the CustomView as a base tag in the XML and inflating/binding the standard way works as expected. It has something to do with how RecyclerView attached the view and the measurement(s) done before, but I have yet to do a deep dive to discover why exactly.
I'm used to the initial methodology, so I'm not sure about what's happening.
have you tried calling initView from onLayout() method?
In your onCreateViewHolder() try setting the params to the view you've created and for width set it to parent.getMeasuredWidth() see if that helps, it solved my problem.
I have a ViewSwitcher and want to add views to it:
// initialize views
final ViewSwitcher switcher = new ViewSwitcher(this);
layMenu = (LinearLayout)findViewById(R.id.menu_main_view);
final LevelPicker levelPicker = new LevelPicker(getApplicationContext());
(//)switcher.addView(layMenu);
(//)switcher.addView(findViewById(R.layout.menu_switcher));
One is a custom view, the other one from XML. I commented one of them, but they both seem to throwIllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
I tried doing several things like putting the views in a 'container' first (another layout), or tried removeView((View)getParent), like I believe the logcat tries to say..
Here's my xml file (in a nutshell):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/menu_main_view">
<TextView>
</TextView>
<LinearLayout>
<Button></Button> //couple of buttons
</LinearLayout>
</LinearLayout> //this is the parent i guess
My first guess was that all childs had to be in 1 parent, which in my case is the LinearLayout. This didn't seem to work.
Thanks
yes any View instance should have only 1 parent according to the source file
{android}/frameworks/base/core/java/android/view/View.java
in order to remove a View instance from its container, you need to do following things:
// View view = ...
ViewParent parent = view.getParent();
if (parent instanceof ViewGroup) {
ViewGroup group = (ViewGroup) parent;
group.removeView(view);
}
else {
throw new UnsupportedOperationException();
}
I guess you invoked Activity.this.setContentView(R.layout....) on the xml layout file. in this case, the parent of the LinearLayout view was another LinearLayout instance provided by a "decorate window".
it's often not a good practice to remove the only child of the "decorate window". you'd better create the children of the ViewSwitcher explicitly:
// Activity.this.setContentView(viewSwitcher);
// final Context context = Activity.this;
final android.view.LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View layMenu = inflater.inflate(R.layout...., null /* container */);
final View menuSwitcher = inflater.inflate(R.layout...., null /* container */);
viewSwitcher.addView(layMenu);
viewSwitcher.addView(menuSwitcher);