Tuesday, January 6, 2009

ScrollTextView - scrolling TextView for Android

I wrote a class derived from Android.Widget.TextView to customize a TextView which text can be scrolled.
The derived class, named ScrollTextView, provides an effect just like the HTML marquee tag.
Moreover, the scrolling can be restarted, paused and resumed.

Here are the source codes.

The key points are:
  • setHorizontallyScrolling() and use android.widget.Scroller to make the text scroll;
  • Use the attached TextPaint of the TextView to measure the length of the text in pixel;
  • setSingleLine() and setEllipsize(null) to make the text not wrapped and ellipsized;
  • Override computeScroll() to restart the scrolling when finished
Here are the source codes.

/*************************************************************************/
package com.dirtybear.android;

import android.content.Context;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
import android.widget.Scroller;
import android.widget.TextView;

public class ScrollTextView extends TextView {

// scrolling feature
private Scroller mSlr;

// milliseconds for a round of scrolling
private int mRndDuration = 250;

// the X offset when paused
private int mXPaused = 0;

// whether it's being paused
private boolean mPaused = true;

/*
* constructor
*/
public ScrollTextView(Context context) {
this(context, null);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}

/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}

/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}

/**
* begin to scroll the text from the original position
*/
public void startScroll() {
// begin from the very right side
mXPaused = -1 * getWidth();
// assume it's paused
mPaused = true;
resumeScroll();
}

/**
* resume the scroll from the pausing point
*/
public void resumeScroll() {

if (!mPaused)
return;

// Do not know why it would not scroll sometimes
// if setHorizontallyScrolling is called in constructor.
setHorizontallyScrolling(true);

// use LinearInterpolator for steady scrolling
mSlr = new Scroller(this.getContext(), new LinearInterpolator());
setScroller(mSlr);

int scrollingLen = calculateScrollingLen();
int distance = scrollingLen - (getWidth() + mXPaused);
int duration = (new Double(mRndDuration * distance * 1.00000
/ scrollingLen)).intValue();

setVisibility(VISIBLE);
mSlr.startScroll(mXPaused, 0, distance, 0, duration);
mPaused = false;
}

/**
* calculate the scrolling length of the text in pixel
*
* @return the scrolling length in pixels
*/
private int calculateScrollingLen() {
TextPaint tp = getPaint();
Rect rect = new Rect();
String strTxt = getText().toString();
tp.getTextBounds(strTxt, 0, strTxt.length(), rect);
int scrollingLen = rect.width() + getWidth();
rect = null;
return scrollingLen;
}

/**
* pause scrolling the text
*/
public void pauseScroll() {
if (null == mSlr)
return;

if (mPaused)
return;

mPaused = true;

// abortAnimation sets the current X to be the final X,
// and sets isFinished to be true
// so current position shall be saved
mXPaused = mSlr.getCurrX();

mSlr.abortAnimation();
}

@Override
/*
* override the computeScroll to restart scrolling when finished so as that
* the text is scrolled forever
*/
public void computeScroll() {
super.computeScroll();

if (null == mSlr) return;

if (mSlr.isFinished() && (!mPaused)) {
this.startScroll();
}
}

public int getRndDuration() {
return mRndDuration;
}

public void setRndDuration(int duration) {
this.mRndDuration = duration;
}

public boolean isPaused() {
return mPaused;
}
}
/*************************************************************************/

Issues to be fixed:
  1. Do not know why it would not scroll sometimes if setHorizontallyScrolling is called in constructor;
  2. The scrolling is not smooth enough. Maybe it's because of the single thread model of Android UI;

Tested on:
Windows XP
Android SDK 1.0 rc2
Android Emulator 1.0
Android Debug Bridge version 1.0.20

13 comments:

  1. >The scrolling is not smooth enough. Maybe it's because of the single thread model of Android UI;

    best way to make it smooth scolling is to derive from SurfaceView and implement necessary methods. That way, we can take the marquee activity on a totally different thread.

    However, this can be heavy-weight. We could also try to make use of off-screen buffers.

    thanks,
    ~Virendra Shakya

    ReplyDelete
  2. Thanks for the info.

    But how can i use this class ( within RemoteViews ) to display scrolling text within TextView on home-screen-widget ?

    Problem description:
    ====================
    I have created a widget to be displayed on android emulator's home screen to display some long text. In my main.xml layout file i've already set TextView properties like singleLine="true", ellipsize="marquee", focusable="true" etc

    ----- main.xml layout file -----------------







    ----------end of main.xml layout file ----


    but still when my widget is displayed on home screen text doesnt move/scroll.

    -- My WidgetProvider.java file ----------

    package com.android.weatherdata;

    import android.util.Log;
    import android.widget.RemoteViews;
    import android.appwidget.AppWidgetManager;
    import android.appwidget.AppWidgetProvider;
    import android.content.Context;
    import android.content.Intent;
    import android.app.PendingIntent;

    public class WeatherWidget extends AppWidgetProvider{

    private static final String TAG = "WeatherWidget";

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
    {
    Log.d("WEATHER-WIDGET", "onUpdate(): ");

    final int N = appWidgetIds.length;
    // Perform this loop procedure for each App Widget that belongs to this provider
    for (int i=0; i<N; i++) {
    int appWidgetId = appWidgetIds[i];

    // Create an Intent to launch ExampleActivity
    Intent intent = new Intent(context, Weather.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

    Log.i("WEATHER-WIDGET", " Create and Attach Text-view click handler ");
    // Get the layout for the App Widget and attach an on-click listener to the button
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main);
    views.setOnClickPendingIntent(R.id.widget_textview , pendingIntent);

    // Tell the AppWidgetManager to perform an update on the current App Widget
    appWidgetManager.updateAppWidget(appWidgetId, views);
    }
    }
    }


    --- End of WidgetProvider file -------------

    In my widget only some part of text is displayed as " Latest Weather Infor" but i would like to write the code in such a way that the complete text should scroll till the complete string/text is displayed.


    Please suggest whether i need to change my layout file or what logic (code snippet) i can write/use so that i can scroll text smoothly.


    Thanks,
    Ravi

    ReplyDelete
  3. Never try my ScrollTextView in Widget.
    Please tar your codes and send to zx.zhangxiong@gmail.com.
    I can have a try.

    ReplyDelete
  4. @xiongzh: Did he send you his code? Could you get it to work?

    ReplyDelete
  5. I have the same problem....
    Id like to see scroll text on widget but is not so easy.....
    Can anyone help me?


    Andrea

    ReplyDelete
  6. You can't use this on a widget full stop.

    ReplyDelete
  7. Thanks for this! It's a great introduction to scrolling.

    ReplyDelete
  8. plaease elaborate how to use this code i am not able see the changes please tell me how to add text to the code......

    ReplyDelete
  9. I am glad to apprehend the accomplished agreeable of this blog and am actual aflame and blessed to say that the webmaster has done a actual acceptable job actuality to put all the advice agreeable and advice at one place.

    ReplyDelete
  10. Wonderful tutorial. Was very helpful. However I want to scroll the text from left to right. How can I do that ?

    ReplyDelete
  11. A small fix. Into resumeScroll() method should have

    if (isFirstTime){
    duration = 0;
    isFirstTime = false;
    }

    and the isFistTime will be true on constructor.

    In this way we don't need to wait so much time at first time that Marquee show up.

    Imagine setRndDuration(40000);

    ReplyDelete
  12. How to increase text size?

    ReplyDelete