简体   繁体   中英

Android: How to remove end blank space from Textview with marquee

I have created TextView with marquee, but I want to remove the blank space between end and start showing the text again.

Here is the screen

http://i.stack.imgur.com/dfdT8.png

Here is my layout

<TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:marqueeRepeatLimit="marquee_forever"
        android:scrollHorizontally="true"
        android:singleLine="true"
        android:text="dkfjdkf jdfjfsfjkfa asdfjakjfdkasf sfdjaskfjdksf sfdjsfjk gfhgfhfai ggfghgf" />

Use :

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

If by blank space you're referring to those black stripes before and after the TextView, you can adjust your layout witdh to android:layout_width="match_parent"

<TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:marqueeRepeatLimit="marquee_forever"
        android:scrollHorizontally="true"
        android:singleLine="true"
        android:text="dkfjdkf jdfjfsfjkfa asdfjakjfdkasf sfdjaskfjdksf sfdjsfjk gfhgfhfai ggfghgf" />

Also, make sure the parent layout of that TextView is not applying any margins

This solution works up until API 28 when they introduced restrictions on non-SDK interfaces .


I ran into the same problem and I couldn't find a solution online. I managed to solve the problem myself by looking at the source code for TextView and referencing other answers that provide an option to control the scroll speed .

Looking at the source code, it will set the gap or blank space to be 1/3 the size of your TextView. So if your TextView is 600 pixels wide, it will create a 200 pixel gap between the start and end of the message. This solution will aim to make that value configurable.

The tricky part is that the logic you need to override is all within private classes, fields, and methods on the super class. This makes the solution far less trivial.

Add this class to your project... CustomTextView.java

public class CustomTextView extends AppCompatTextView {

    private static final float NEW_GAP = 0F;

    Object marqueeObject;
    Field mStatusField;
    Field mGhostStartField;
    Field mMaxScrollField;
    Field mGhostOffsetField;
    Field mFadeStopField;
    Field mMaxFadeScrollField;

    public CustomTextView(Context context) {
        super(context);
        setSelected(true);
    }

    public CustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setSelected(true);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setSelected(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        try {
            initMarqueeObject();

            if (didMarqueeRestart()) {
                // We need to update the values each time it restarts
                updateMarqueeFieldValues();
            }
        }
        catch(Exception exception) {
            exception.printStackTrace();
        }

        super.onDraw(canvas);
    }

    private void initMarqueeObject() throws Exception {
        if (marqueeObject == null) {
            Field marqueeField = getClass().getSuperclass().getSuperclass().getDeclaredField("mMarquee"); // use if extending from AppCompatTextView
            // Field marqueeField = getClass().getSuperclass().getDeclaredField("mMarquee"); // use if extending from TextView
            marqueeField.setAccessible(true);
            marqueeObject = marqueeField.get(this);
            initMarqueeFields();
        }
    }

    private void initMarqueeFields() throws Exception {
        mStatusField = marqueeObject.getClass().getDeclaredField("mStatus");
        mStatusField.setAccessible(true);
        mGhostStartField = marqueeObject.getClass().getDeclaredField("mGhostStart");
        mGhostStartField.setAccessible(true);
        mMaxScrollField = marqueeObject.getClass().getDeclaredField("mMaxScroll");
        mMaxScrollField.setAccessible(true);
        mGhostOffsetField = marqueeObject.getClass().getDeclaredField("mGhostOffset");
        mGhostOffsetField.setAccessible(true);
        mFadeStopField = marqueeObject.getClass().getDeclaredField("mFadeStop");
        mFadeStopField.setAccessible(true);
        mMaxFadeScrollField = marqueeObject.getClass().getDeclaredField("mMaxFadeScroll");
        mMaxFadeScrollField.setAccessible(true);
    }

    private boolean didMarqueeRestart() throws Exception {
        byte currentState = mStatusField.getByte(marqueeObject);
        return currentState == 0x1; // 0x1 is the byte object for the MARQUEE_STARTING state
    }

    private void updateMarqueeFieldValues() throws Exception {
        float textWidth = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
        float originalGap = textWidth / 3.0F;
        // We have to calculate the lineWidth based on the original value
        float originalValueStartValue = mGhostStartField.getFloat(marqueeObject);
        float lineWidth = originalValueStartValue + textWidth - originalGap;
        float mGhostStart = lineWidth - textWidth + NEW_GAP;
        mGhostStartField.setFloat(marqueeObject, mGhostStart);
        mMaxScrollField.setFloat(marqueeObject, mGhostStart + textWidth);
        mGhostOffsetField.setFloat(marqueeObject, lineWidth + NEW_GAP);
        mFadeStopField.setFloat(marqueeObject, lineWidth);
        mMaxFadeScrollField.setFloat(marqueeObject, mGhostStart + lineWidth + lineWidth);
    }
}

You can now easily adjust the extra blank space using the NEW_GAP variable. In this example, it is set to 0 pixels. Set that constant to any pixel value that you wish to use.

NOTE: this solution is extending from AppCompatTextView . If you are extending from TextView you should uncomment the relevant code in the initMarqueeObject() method.

Then just just use this new class in place of the old TextView in XML...

<com.yourpackagename.CustomTextView
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:singleLine="true"
    android:scrollHorizontally="true"
    android:ellipsize="marquee"
    android:marqueeRepeatLimit="marquee_forever"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

There is no need to call setSelected(true) in your code as this is done internally within the new class.

This may not work forever as it will break if they rename certain variables or change the logic of TextView in future releases. But it has worked great on all versions that I have tested it on.

Hopefully this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM