Home » Android » java – Loading a TextView from xml into a TextSwitcher

java – Loading a TextView from xml into a TextSwitcher

Posted by: admin June 15, 2020 Leave a comment

Questions:

I’ve been trying to setup a TextSwitcher with the TextView loading in from an xml file. I have this basic TextSwitcher example that uses an empty TextView working ok:

.java

public class TextSwitcherTest extends Activity 
{
    private TextSwitcher textSwitcher;

    private ViewFactory viewFactory = new ViewFactory() 
    {
        public View makeView() 
        {
            TextView textView = new TextView(TextSwitcherTest.this);

            return textView;
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        textSwitcher = (TextSwitcher) findViewById(R.id.textSwitcher);

        textSwitcher.setFactory(viewFactory);

        Animation in = AnimationUtils.loadAnimation(this,android.R.anim.fade_in);
        Animation out = AnimationUtils.loadAnimation(this,android.R.anim.fade_out);

        textSwitcher.setInAnimation(in);
        textSwitcher.setOutAnimation(out);

        textSwitcher.setText("test ok");
    }    
}

main.xml

<?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" >

    <TextSwitcher 
        android:id="@+id/textSwitcher"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

But when I change the code to try and load a TextView from an xml file, it spits out a pile of errors. Here’s the amended code:

.java

public class TextSwitcherTest extends Activity 
{
    private TextSwitcher textSwitcher;

    private ViewFactory viewFactory = new ViewFactory() 
    {
        public View makeView() 
        {
            LayoutInflater inflater = LayoutInflater.from(TextSwitcherTest.this);

            TextView textView = (TextView) inflater.inflate(R.id.textView,null);

            return textView;
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        textSwitcher = (TextSwitcher) findViewById(R.id.textSwitcher);

        textSwitcher.setFactory(viewFactory);

        Animation in = AnimationUtils.loadAnimation(this,android.R.anim.fade_in);
        Animation out = AnimationUtils.loadAnimation(this,android.R.anim.fade_out);

        textSwitcher.setInAnimation(in);
        textSwitcher.setOutAnimation(out);

        textSwitcher.setText("test ok");
    }    
}

textview.xml

<?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" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

</LinearLayout>

Can anyone offer a suggestion where I’m going wrong? The code compiles ok but I’m sure it’s with the LayoutInflater being misused in some way?


error messages (it didn’t display the ’11 more’):

07-22 03:51:24.096: W/dalvikvm(580): threadid=1: thread exiting with uncaught exception (group=0x409c01f8)
07-22 03:51:24.137: E/AndroidRuntime(580): FATAL EXCEPTION: main
07-22 03:51:24.137: E/AndroidRuntime(580): java.lang.RuntimeException: Unable to start activity ComponentInfo{test09.TextSwitcher01/test09.TextSwitcher01.TextSwitcherTest}: android.content.res.Resources$NotFoundException: Resource ID #0x7f050001 type #0x12 is not valid
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1956)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1981)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.app.ActivityThread.access$600(ActivityThread.java:123)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1147)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.os.Handler.dispatchMessage(Handler.java:99)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.os.Looper.loop(Looper.java:137)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.app.ActivityThread.main(ActivityThread.java:4424)
07-22 03:51:24.137: E/AndroidRuntime(580):  at java.lang.reflect.Method.invokeNative(Native Method)
07-22 03:51:24.137: E/AndroidRuntime(580):  at java.lang.reflect.Method.invoke(Method.java:511)
07-22 03:51:24.137: E/AndroidRuntime(580):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
07-22 03:51:24.137: E/AndroidRuntime(580):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
07-22 03:51:24.137: E/AndroidRuntime(580):  at dalvik.system.NativeStart.main(Native Method)
07-22 03:51:24.137: E/AndroidRuntime(580): Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x7f050001 type #0x12 is not valid
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.content.res.Resources.loadXmlResourceParser(Resources.java:2110)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.content.res.Resources.getLayout(Resources.java:857)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.view.LayoutInflater.inflate(LayoutInflater.java:394)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
07-22 03:51:24.137: E/AndroidRuntime(580):  at test09.TextSwitcher01.TextSwitcherTest$1.makeView(TextSwitcherTest.java:23)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.widget.ViewSwitcher.obtainView(ViewSwitcher.java:80)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.widget.ViewSwitcher.setFactory(ViewSwitcher.java:99)
07-22 03:51:24.137: E/AndroidRuntime(580):  at test09.TextSwitcher01.TextSwitcherTest.onCreate(TextSwitcherTest.java:38)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.app.Activity.performCreate(Activity.java:4465)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
07-22 03:51:24.137: E/AndroidRuntime(580):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1920)
07-22 03:51:24.137: E/AndroidRuntime(580):  ... 11 more
How to&Answers:

There are several things wrong with your code:

First, the LayoutInflater.inflate() method expects the id of a layout file in the form of R.layout.layout_name and not in the form of R.id.some_id:

TextView textView = (TextView) inflater.inflate(R.layout.textView, null);

Second, the code you use will throw a ClassCastException because the root of the inflated layout is a LinearLayout and not a TextView as you try to cast it. Your code should be:

LinearLayout ll = (Linearlayout) inflater.inflate(R.layout.textView, null);

But even the line above will not work because, as its name suggest, a TextSwitcher will take only children of type TextView, nothing can come between the TextSwitcher and the two TextView children. The correct code to use will be in the end:

Layout for textview.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/textView"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello" />

And the viewFactory field will be:

private ViewFactory viewFactory = new ViewFactory() {
        public View makeView()  {
            LayoutInflater inflater = LayoutInflater.from(TextSwitcherTest.this);
            TextView textView = (TextView) inflater.inflate(R.layout.textView, null);
            return textView;
        }
};

Answer:

Pretty simple using mostly XML.

In your XML add a TextSwitcher root view and two TextViews within it:

<TextSwitcher
    android:id="@+id/textswitcher_id"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentEnd="true"
    android:inAnimation="@android:anim/fade_in"
    android:outAnimation="@android:anim/fade_out">

    <TextView
        android:id="@+id/tv_onboarding_next"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text1" />

    <TextView
        android:id="@+id/tv_onboarding_letsgo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text2" />

</TextSwitcher>

Then in code, wherever you want the animation action to occur:

Using Kotlin:

textswitcher_id.setText(getString("Text2"))

// and somewhere else, switch it back:

textswitcher_id.setText(getString("Text1"))

Or in Java:

findViewById(R.id.textswitcher_id).setText(getString("Text2"));

// and somewhere else, switch it back:

findViewById(R.id.textswitcher_id).setText(getString("Text1"));

Note:

  • Within the XML you can also use two includes rather TextView twice, see: https://stackoverflow.com/a/42724239/1852191

  • The reason we have the default text in the XML even though we set it programmatically later is because on the first view it will be blank, until it’s set

Answer:

Actually, you don’t need to set factory if you put two TextViews inside your TextSwitcher in layout XML. This worked for me:

<TextSwitcher
    android:id="@+id/id1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:inAnimation="@android:anim/fade_in"
    android:outAnimation="@android:anim/fade_out">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:lines="1"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:lines="1"/>
</TextSwitcher>

(no additional configuration done in code, just inflate and findViewById)

However, you have to duplicate the attributes across the TextViews. This might be avoided by moving the TextView to the separate file and then including it twice:

<TextSwitcher
    android:id="@+id/id1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:inAnimation="@android:anim/fade_in"
    android:outAnimation="@android:anim/fade_out">

    <include layout="@layout/titlebar" />
    <include layout="@layout/titlebar" />
</TextSwitcher>

Other way to avoid duplication would be creating a custom style and applying it to both TextViews.

Answer:

I came up with this solution, which for me is the closest to defining the TextView directly in the XML:

First, define the TextSwitcher with two TextView objects inside it, with your custom definition:

        <TextSwitcher
            android:fontFamily="sans-serif-condensed"
            android:id="@+id/detail_date_textswitch"
            android:layout_height="wrap_content"
            android:layout_width="match_parent">
            <TextView
                android:id="@+id/detail_date_textview1"
                android:textColor="@color/grey_700"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="20sp"/>
            <TextView
                android:id="@+id/detail_date_textview2"
                android:textColor="@color/grey_700"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="20sp"/>
        </TextSwitcher>

After that, in your code, define the following factory for your TextSwitcher object:

    textSwitcher.setFactory(new ViewSwitcher.ViewFactory() {
        boolean isFirst = true;
        @Override
        public View makeView() {
            if (isFirst) {
                isFirst = false;
                TextView textView = (TextView) mDateView.findViewById(R.id.detail_date_textview1);
                mDateView.removeViewAt(0);
                return textView;
            } else {
                TextView textView = (TextView) mDateView.findViewById(R.id.detail_date_textview2);
                mDateView.removeViewAt(0);
                return textView;
            }
        }
    });

I won’t say it’s not kind of tricky, but it worked for me.