Reputation: 298
This was asked elsewhere, but the solution did not work for me. So posing it again with more context. The issue is that an activity contains a scrolling music title text view which is disrupted by an updating elapsed time counter text view.
I have these two TextView widgets in my activity layout (although they are encompassed by other layout containers).
<TextView android:id="@+id/v_current_time"
android:minWidth="26dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="right|center_vertical"
android:singleLine="true"
android:textSize="12sp"
android:enabled="false"
/>
<TextView android:id="@+id/v_track_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="normal"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:enabled="true"
/>
The music title is dynamically set to (in the test case) a long line of text. If I never update the text for the current time with the following line, the music title will happily scroll forever no matter how I interact with my pause and play buttons.
tvCurrentTime.setText(DataFormat.formatTime(progress));
But if I do set the text for the current time, the music title will stop. Pressing my pause button somehow kicks scrolling back into gear, but pressing play will cause the current time to update and stop it again.
Per the suggestion in this thread, I attempted to couple the setting of the time text with re-enabling of the scrolling title as follows:
tvCurrentTime.setText(DataFormat.formatTime(progress));
tvTitle.setEnabled(true);
This has no effect on the failure other than to reset the scroller each time it restarts.
Is there some detail that I am missing, or any other thoughts as to what could be going wrong? Thanks a lot!
Upvotes: 8
Views: 7142
Reputation: 556
We had an adapter with multiple view types, and first item was one with marquee TextView. After scrolling back to top of the screen text was not shown (we've called textView.isSelected == true).
Also, issue was not the RelativeLayout, there was no need to wrap TextView with LinearLayout, as current structure in layout is:
RelativeLayout
Button
TextView
Below is method from TextView to start marquee:
private void startMarquee() {
// Do not ellipsize EditText
if (getKeyListener() != null) return;
if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
return;
}
if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
&& getLineCount() == 1 && canMarquee()) {
if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
final Layout tmp = mLayout;
mLayout = mSavedMarqueeModeLayout;
mSavedMarqueeModeLayout = tmp;
setHorizontalFadingEdgeEnabled(true);
requestLayout();
invalidate();
}
if (mMarquee == null) mMarquee = new Marquee(this);
mMarquee.start(mMarqueeRepeatLimit);
}
}
It requires for view to have focus or to be selected to start marquee.
In TextView.onFocusChanged(...) the startStopMarquee(focused) method is called and it will trigger the scroll animation. Issue we had with this approach is that we needed to request focus by using postDelayed, which might cause some issues.
After checking what TextView.setSelected(boolean) method was doing, it was clear why textView.isSelected = true was not triggering animation. Inside it it was checking previous isSelected state, and it would handle startMarquee() or stopMarquee() if new isSelected state was different from the previous one.
Solution was to change selected state to false and after that to set it to true which worked perfectly.
Below are both methods, setSelected, and onFocusChanged.
@Override
public void setSelected(boolean selected) {
boolean wasSelected = isSelected();
super.setSelected(selected);
if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
if (selected) {
startMarquee();
} else {
stopMarquee();
}
}
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (isTemporarilyDetached()) {
// If we are temporarily in the detach state, then do nothing.
super.onFocusChanged(focused, direction, previouslyFocusedRect);
return;
}
if (mEditor != null) mEditor.onFocusChanged(focused, direction);
if (focused) {
if (mSpannable != null) {
MetaKeyKeyListener.resetMetaState(mSpannable);
}
}
startStopMarquee(focused);
if (mTransformation != null) {
mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
Upvotes: 0
Reputation: 48
In java code, doing tvTitle.setSelected(true);
(here, tvTitle is your sliding TextView variable) worked for me. Doing this, seems to make it focused again. So worked like a charm.
Upvotes: 0
Reputation: 298
(Promoting comment above to an answer) Turns out that the TextView XML configs above work fine without any runtime changes (to reset enabled=true or whatever) IF I get rid of the RelativeLayout's in my layout file and replace them with LinearLayout's. And neither suggestion 1 or 2 above (not sure about 3) works if I don't. That seems like a subtle and bogus undocumented failure of RelativeLayout.
Upvotes: 2
Reputation: 1029
There is another way to solve this without removing all RelativeLayout.
You can simply wrap the marquee TextView with a LinearLayout inside the RelativeLayout as a container.
Upvotes: 7
Reputation: 23977
Marquee is problematic. When TextView (or the Window containing the TextView)loses focus the marquee is stopped and reset (see the sources for TextView). I guess you have 3 possible solutions here:
android:focusable="false"
in all other Views in your layout. That way your TextView shouldn't lose focus. But that's probably not the solution you want. onFocusChanged()
and onWindowFocusChanged()
to prevent marquee from being stopped and reset.Upvotes: 7