Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android ViewStub change layouts programmatically

This is my use case:

I want to change my inflated layout at run time, say first I inflate layout a, then after some time I want to show layout B, then layout C etc.

I read somewhere that rather than including layouts in main layout and then hiding/unhiding I should use viewstub and inflate.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ViewStub
        android:id="@+id/layout_stub"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

My issue now is when I inflate the first layout it works fine but the next time when I try to inflate the second layout I get the stub null.

ViewStub stub = (ViewStub) findViewById(R.id.layout_stub);
        stub.setLayoutResource(layoutId);
        View inflated = stub.inflate();

My understanding is the Viewstub is a container in which the layouts are being loaded, if so why am I not getting the ViewStub when trying to load the second layout? (So this means when I inflated the first layout (A) the layout in which the ViewStub was placed was removed completely?)

I'm looking for any pointers to implementing my usecase with Viewstub or alternatives.

like image 249
ViVekH Avatar asked May 25 '16 15:05

ViVekH


2 Answers

A ViewStub is a placeholder, which is replaced by an inflated layout as soon as ViewStub.inflate() is called. It doesn't make sense to call inflate a second time, as the ViewStub will no longer be in the hierarchy. Instead, you should obtain a reference to your LinearLayout, remove its views, and add your second layout as a child.

ViewStub stub = (ViewStub) findViewById(R.id.layout_stub);
LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
stub.setLayoutResource(layoutId);
stub.inflate(); // inflate 1st layout

ll.removeAllViews(); // remove previous view, add 2nd layout
ll.addView(LayoutInflater.from(context).inflate(secondLayoutId, ll, false));
like image 176
fractalwrench Avatar answered Sep 22 '22 10:09

fractalwrench


Yes, I think you can easily replace it with another ViewStub and lazily inflate your new layout in this way:

for Java

 public static ViewStub deflate(View view) {
    ViewParent viewParent = view.getParent();
    if (viewParent != null && viewParent instanceof ViewGroup) {
        int index = ((ViewGroup) viewParent).indexOfChild(view);
        int inflatedId = view.getId();
        ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
        ((ViewGroup) viewParent).removeView(view);
        Context context = ((ViewGroup) viewParent).getContext();
        ViewStub viewStub = new ViewStub(context);
        viewStub.setInflatedId(inflatedId);
        viewStub.setLayoutParams(layoutParams);
        ((ViewGroup) viewParent).addView(viewStub, index);
        return viewStub;
    } else {
        throw new IllegalStateException("Inflated View has not a parent");
    }
}

or Kotlin with an extension

fun ViewStub.deflate(view: View): ViewStub {
    val viewParent = view.parent

    if (viewParent != null && viewParent is ViewGroup) {
        val index = viewParent.indexOfChild(view)
        viewParent.removeView(view)
        val viewStub = ViewStub(context).apply {
            inflatedId = [email protected]
            layoutParams = [email protected]
        }
        viewParent.addView(viewStub, index)
        return viewStub
    } else {
        throw IllegalStateException("Inflated View has not a parent")
    }
}

Check out the gist

like image 29
leon Avatar answered Sep 21 '22 10:09

leon