2

I have a TabHost with several tabs, which content is defined as a FrameLayout, which wraps a TextView, each one of them having a lot of text, more than it can be shown within the screen layout, so I had to enable vertical scrolling for every tab.

The main thing is that those tabs are created dynamically (programatically), so I have to specify all the options this way:

final SoftReference<TabHost> th = new SoftReference<TabHost>((TabHost) ((Activity) globvars.getContext()).findViewById(android.R.id.tabhost));

final TabSpec setContent = th.get().newTabSpec(tag).setIndicator(tabview).setContent(new TabContentFactory() {
  public View createTabContent(String tag) {
    view.setBackground(globvars.getContext().getResources().getDrawable(R.drawable.rounded_edges));
    view.setPadding(25, 25, 25, 25);
    view.setTextColor(Color.WHITE);
    view.setLines(50);
    view.setMaxLines(maxLines);
    view.setEllipsize(TextUtils.TruncateAt.START);
    view.setHorizontalScrollBarEnabled(false);
    view.setVerticalScrollBarEnabled(true);
    view.setMovementMethod(new ScrollingMovementMethod());
    view.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
    view.setVerticalFadingEdgeEnabled(true);
    view.setGravity(Gravity.BOTTOM);
    view.setOverScrollMode(1);
    view.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 11);
    view.setTypeface(Typeface.MONOSPACE);
    return view;
  }
});

Using this approach, I can scroll backwards and forwards indeed, but visually the scrollbar is not shown. I'm mean this bar:

Virtual Scroll Bar

Am I missing something? Does the scrollbar have to be defined by a customized drawable by imperative?

Thanks!

------ EDIT (12-31-2013) ------

I've been looking around and still can't find any solution to this. I've tried as many combinations of parameters as I was able to find, but no result. Particularly, I've tried this and also wrapping the FrameLayout within a ScrollView, but instead of showing a scrollbar at the TextView itself, the whole TextView is wrapped within a scrollbar and grows when buffer gets bigger and bigger, that's not what I want.

Any help appreciated!

------ EDIT (01-17-2014) ------

Well, at this point, I can assure I've tried any logical step (well, at least to me) to make this work and still can't! I clarify that I have about 7-8 additional activities and I have no trouble with scrolling in any of them. I just work with TabHost in this one, though. So I'm starting a bounty on this, because that's already endangering my mental sanity.

I'm posting my layout below:

<LinearLayout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/fondo_imagen"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

  <LinearLayout
            android:id="@+id/TabContainer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="99"
            android:orientation="vertical">
    <TabHost
            android:id="@+android:id/tabhost"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
      <LinearLayout
            android:id="@+id/TabLinearLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
        <!-- Horizontal scrolling for the tab widget -->
        <HorizontalScrollView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:fillViewport="true"
            android:scrollbars="none">
          <TabWidget
            android:id="@+android:id/tabs"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        </HorizontalScrollView>
        <FrameLayout
            android:id="@+android:id/tabcontent"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp" />
      </LinearLayout>
    </TabHost>
  </LinearLayout>

  <!-- Some additional LinearLayouts that don't have anything to do with the tab widget -->
  ...
</LinearLayout>

------ EDIT (01-19-2014) ------

Ok, based on corsair992's answer, I could finally get this working. My real mistake was assuming that even the method that creates the tab (posted above) receives a TextView as parameter, working with a View in the TabSpec definition would be casting it to the TextView. So indeed, I wasn't aware I was actually setting the scrollbars on a View (didn't know the View class didn't provide a public programatic method to configure scrollbars neither), so I followed corsair992's advice and created a separate layout with this content:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tabsContent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="3dp"
    android:background="@drawable/rounded_edges"
    android:gravity="bottom"
    android:padding="8dp"
    android:scrollHorizontally="false"
    android:scrollbarStyle="insideOverlay"
    android:scrollbars="vertical"
    android:textColor="#FFFFFF"
    android:textSize="11sp"
    android:typeface="monospace"
    tools:ignore="SmallSp" />

So now, instead of calling the above method which sets all those attributes to the View, I simply inflate this layout and set the MovementMethod:

final TabSpec setContent = th.get().newTabSpec(tag).setIndicator(tabview).setContent(new TabContentFactory() {
  public View createTabContent(String tag) {
    // tabs_content is the layout above
    final View ll_view = LayoutInflater.from(globvars.getContext()).inflate(R.layout.tabs_content, null);
    final TextView view = (TextView) ll_view.findViewById(R.id.tabsContent);

    view.setMovementMethod(new ScrollingMovementMethod());

    return ll_view;
  }
});
Community
  • 1
  • 1
nKn
  • 13,691
  • 9
  • 45
  • 62
  • 1
    Did I read correctly that you're not using an actual `TextView`, you're using the `FrameLayout` and then casting it? Why wouldn't you use a `TextView`? – anddev84 Jan 18 '14 at 01:18
  • 1
    I didn't explain myself correctly, you're right. The `FrameLayout` actually isn't cast to a `TextView`, the `FrameLayout` **wraps** the `TextView` inside it, which is actually what I create programatically in my code. I changed that in the question. Thanks! – nKn Jan 18 '14 at 10:06
  • Why are you storing the `TabHost` in a `SoftReference`? If this reference is out of the scope of the `Activity`, then you should use a `WeakReference` instead. There is no point in retaining a `View` after it's `Activity` has been destroyed, and doing so also prevents the `Activity` from being garbage collected, since `View`s hold a reference to their `Context`. Also, you should be checking for null when obtaining objects from soft or weak references. – corsair992 Jan 18 '14 at 22:50
  • @corsair992 I just defined the `SoftReference` to facilitate the GC's task, this application is a bit complex and has hundreds of threads which reference to the `TabHost`, so defining the `SoftReference` just tells the GC it can be freed a reasonable time after last use. You're right I should test whether the object is null or not, I didn't have any issues but it's better to do it, so I'll change it. I've also tried a `WeakReference` but as it was freed too early, it leaded to a lot of NPExceptions, so I changed it to a `SoftReference` which works good. – nKn Jan 19 '14 at 12:47
  • 1
    If you check for null whenever you obtain the `TabHost` (which you should be doing in any case), then holding a weak reference to it should be fine. Relying on a soft reference to retain an object for a reasonable time is a dangerous practice; it may work in normal cases, but it will fail when there is intensive memory usage. Also, since the soft reference will be retaining the whole `View` hierarchy and the `Activity` itself in memory (along with everything they hold a reference to), it might cause other more important softly referenced objects to be garbage collected instead. – corsair992 Jan 19 '14 at 13:25
  • @corsair992 Thanks for those advices, I appreciate them. I'll fit my code to this, and of course I'll test whether the reference is null or not. – nKn Jan 19 '14 at 13:45

1 Answers1

2

It appears that the base View class does not provide a public method for initializing scroll bars if they are not specifically enabled in an XML layout resource. However, there is no reason you can't define your tab content in an XML resource, enable vertical scroll bars by setting the android:scrollbars attribute to vertical, and inflate it from the TabContentFactory dynamically.

Something like this in your case:

public View createTabContent(String tag) {
    Activity activity = (Activity) globvars.getContext();
    TextView view = (TextView) LayoutInflater.from(activity).inflate(R.layout.mytabcontent,
            (ViewGroup) activity.findViewById(android.R.id.tabcontent), false);
    view.setMovementMethod(new ScrollingMovementMethod());
    view.setText("My Text");
    return view;
}
corsair992
  • 3,050
  • 22
  • 33
  • Thanks, I'll try this today or tomorrow and I'll comment results. – nKn Jan 19 '14 at 12:42
  • It worked like a charm, I updated my question with the solution. Thanks a lot! – nKn Jan 19 '14 at 15:39
  • @NKN: Your enclosing `LinearLayout` in the tab content layout seems to serve no purpose; you should move the `TextView` to the root of your XML layout for a more lightweight layout hierarchy. – corsair992 Jan 19 '14 at 16:15
  • True, in this case it doesn't have a purpose. Changed in the question too. – nKn Jan 19 '14 at 16:34