开发者

Why does TextView.setText cause the enclosing ScrollView to scroll?

I've got this odd problem which is happening on 1.6, 2.2, and a MyTouch 3G Slide (which is API #7, and listed as "2.1-Update1" in the Android Device Chooser). If anyone can explain what I'm doing wrong & how to fix it (or possibly confirm that this is an Android bug), I'd greatly appreciate it!

The basic idea for my app is to make a stopwatch-sort of thing, in that the user can tap a button to start a timer, then tap it again to stop (pause) the timer; further taps alternate between resuming the timer and pausing the timer.

I've got a top-level ScrollView which contains a RelativelLayout, which contains a bunch of widgets. The first widget is a HUGE button (so that it's easy to press), which pushes all my other widgets below the bottom of the screen. This is intentional, as I want to rely on the ScrollView (and an on-screen reminder to the user) to make the rest of the input options available.

I've got a simple state-machine type setup, where mState is the current mode (STATE_TIMER_NOT_STARTED before the user presses any buttons, ...RUNNING after the first press, and then ...PAUSED after the second, back to ...RUNNING after the third, etc, etc).

All this works great EXCEPT that when the timer is running, and the user presses the start/stop/resume button again, the ScrollView will scroll down a ways. I am NOT issuing this command (I don't even have a reference to ScrollView object), and I'm not sure why it's doing this.

REPRO: Compile + run the below samples. When the app starts, press the 'Start Timing' button. Use your thumb (or the mouse) to touch-drag the screen upwards (so you can see the RatingBar), then drag it back downwards (so the button is again completely on-screen). Tap the button (which now reads 'PauseTiming') again, and it'll jump down a bit. It should NOT be jumping/scrolling down, since there's no statement (that I can see) that tells it to scroll down. As near as I can tell, it's the setText that causes the scrolling ( when I comment those lines out, no scrolling occurs).

WHAT I'M ASKING FOR: If I'm doing something dumb & you could point out what it is, I'd really appreciate it! :) *** I wonder if 'touch mode' might have something to do with this, since it does NOT appear to happen (in the emulator) when I use the mouse's scroll wheel to move the panel upwards (i.e.,instead of the simulated finger-dragging). I can't find a whole lot on touch-mode, and nothing specific on focus/selection in touch mode within a ScrollView

If you can confirm that this error occurs for you too, that would be ok, too (since misery loves company.AHEM I mean, since it might help confirm that it's not just me :) ).

MyTestApp.java

package bug.android.scrollview;

import android.app.Activity;
import android.os.Bundle;
import android.text.format.Time;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;

public class MyTestApp extends Activity {

    public final static int STATE_TIMER_NOT_STARTED = 1;
    public final static int STATE_TIMER_RUNNING = 2;
    public final static int STATE_TIMER_PAUSED = 3;
    private int mState;

    Time t = new Time();

    private Time data = new Time();

    private Button btnStartStopResume;
    private TextView lblSpacer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.new_time_entry);

        btnStartStopResume = (Button) findViewById(R.id.btnStartStopResume);

        // Set the button's size so that the other info will also be visible
        Display display = ((WindowManager) getSystemService(WINDOW_SERVICE))
                .getDefaultDisplay();
        // This is such a hack, but the windowScroller doesn't appear to
        // have a height at this point in the lifecycle (nor in 'onResume' :( )
        btnStartStopResume.setHeight(display.getHeight() - 200);

        lblSpacer = (TextView) findViewById(R.id.lblSpacer);

        reset();
    }

    public void doStartStopResume(View v) {
        if (mState == MyTestApp.STATE_TIMER_NOT_STARTED) {

            mState = MyTestApp.STATE_TIMER_RUNNING;

            data.setToNow();

        } else if (mState == MyTestApp.STATE_TIMER_RUNNING) {
            mState = MyTestApp.STATE_TIMER_PAUSED;

            String s = getString(R.string.add_scroll_down_to_add);
            lblSpacer.setText(s);
        } else if (mState == MyTestApp.STATE_TIMER_PAUSED) {

            mState = MyTestApp.STATE_TIMER_RUNNING;
        }
    }
    public void doReset(View v) {
    }

    public void doNewRunClick(View v) {
    }

    public void doAddTiming(View v) {
    }

    public void reset() {
        mState = STATE_TIMER_NOT_STARTED;
    }
}

new_time_entry.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/windowScroller"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
    <RelativeLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
    >
        <Button
            android:id="@+id/btnStartStopResume"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dip"
            android:text="Start Timing"
            android:textSize="40dp"
            android:height="290dp"
            android:onClick="doStartStopResume" />

        <TextView
            android:id="@+id/lblSpacer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/btnStartStopResume"
            android:layout_centerHorizontal="true"
            android:text="@string/add_scroll_down_for_more" />

        <TextView
            android:id="@+id/lblTimeStartLabel"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/lblSpacer"
            android:layout_alignParentLeft="true"
            android:clickable="true"
            android:onClick="adjustStartTime"
            android:text="Start of this run:"
            android:textSize="8dp" />

        <TextView
            android:id="@+id/lblTimeStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/lblTimeStartLabel"
            android:layout_alignParentLeft="true"
            android:clickable="true"
            android:onClick="adjustStartTime"
            android:text="--:--:-- --"
            android:textColor="#FFFFFF"
            android:textSize="26dp" />

        <TextView
            android:id="@+id/lblElapsedLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/lblSpacer"
            android:layout_alignRight="@id/lblSpacer"
            android:layout_marginRight="5dp"
            android:text="Elapsed Time:"
            android:textSize="8dp" />

        <TextView
            android:id="@+id/lblTimeElapsed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/lblElapsedLabel"
            android:layout_alignRight="@id/lblSpacer"
            android:layout_marginRight="5dp"
            android:textColor="#99ff66"
            android:text="-- m -- sec"
            android:textSize="26dp" 
            android:layout_marginBottom="10dip"/>

        <CheckBox
            android:id="@+id/chkNewRun"
            android:onClick="doNewRunClick"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/lblTimeElapsed"
            android:text="This is a new run of timings"
            android:layout_marginBottom="10dip" />

        <TextView
            android:id="@+id/lblIntensity"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Intensity (1 = none 5 = max)"
            android:layout_below="@id/chkNewRun" />

        <RatingBar
            android:id="@+id/rbIntensity"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/lblIntensity"
            android:numStars="5"
            android:rating="2"
            android:layout_marginBottom="5dip" />

        <TextView
            android:id="@+id/lblNotes"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Notes:"
            android:layout_below="@id/rbIntensity" />

        <EditText
            android:id="@+id/txtNotes"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:background="@android:drawable/editbox_background"
            android:layout_below="@id/lblNotes"
            android:layout_marginBottom="10dip" />


        <Button
            android:id="@+id/btnReset"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/txtNotes"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="10dip"
            android:layout_marginRight="10dip"
            android:text="Reset"
            android:onClick="doReset" />

        <Button
            android:id="@+id/btnOk"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/txtNotes"
            android:layout_toRightOf="@id/btnReset"
            android:layout_alignParentRight="true"
            android:layout_marginLeft="10dip"
            android:layout_marginRight="10dip"
            android:text="Add Timing To List"
            android:onClick="doAddTiming" />
    </RelativeLayout>
</ScrollView>

strings.xml

<?xml version="1.0" e开发者_开发问答ncoding="utf-8"?>
<resources>

    <string name="app_name">Timer</string>
    <string name="dlg_edit_timing_title">Edit A Timing</string>
    <string name="add_scroll_down_for_more">&lt; Scroll down for more options! &gt;</string>
    <string name="add_scroll_down_to_add">&lt; Scroll down to save this timing! &gt;</string>
    <string name="start_timing">Start Timing\n\n</string>
    <string name="stop_timing">Pause Timing\n\n</string>
    <string name="resume_timing">Resume Timing\n\n</string>
</resources>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="bug.android.scrollview"
    android:versionCode="1"
    android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
    <activity android:name=".MyTestApp"
        android:label="@string/app_name">
        <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="5" />
</manifest>

UPDATE 1: Adding

        if(  btnStartStopResume.isInTouchMode() )
            Toast.makeText(this, "TOUCH MODE", 2000);
        else
            Toast.makeText(this, "NOT touch mode", 2000);

then setting breakpoints in the debugger confirms that the button is always in touch mode (regardless of whether I finger-drag the panel up/down, or mouse-wheel it up/down). So it's a combination of being in touch-mode AND finger-dragging the panel after the 2nd button-press (i.e, when the app is in 'stopped/paused timing' mode) that's causing the odd extra-timing in subsequent pauses.

UPDATE 2: I just noticed that it's scrolling down to the EditText, and no further. It looks like when you move the panel down the EditText gets the selection, and after the click event the ScrollView scrolls back to the thing that has the selection. Seems to explain why the mouse-wheel approach doesn't have this problem (it moves the selection/focus back up to the button).


Old thread, but I thought I'd add this in case anyone's searching around like me. I had the same problem, but just clearing focus didn't help. This is what finally solved it for me:

editText.clearFocus();
editText.setTextKeepState(text);

Hope this helps someone. TextView docs


Ok, I've got handle on what's going on, and enough of an idea of how to work around it that I thought I might as well post this here, as an answer:

If you call clearFocus on the EditText before any call to setText (which is both the button event handler, and a couple of timers I'm running via the thread's Handler in the 'real' version of this program), then everything works they way I expect (no weird auto-scrolling).

If you wanted to use this solution you'd need to clear the focus for anything that could get the focus, though, which makes it a lame solution - I'll try to keep looking at this. Under the ScrollView docs there's a method named onRequestFocusInDescendants with the cryptic note "When looking for focus in children of a scroll view, need to be a little more careful not to give focus to something that is scrolled off screen. This is more expensive than the default ViewGroup implementation, otherwise this behavior might have been made the default." which may point to what's going on here...


I tried solutions on this page and they didn't work for me. I had a scrollview that scrolled every time I clicked a refresh button in the view. What I ended up doing was making a title that is just to the left of the button focusable and set that item to be focused in my button callback.

Part of my onCreateView in my Fragment:

final View view = inflater.inflate(R.layout.fragment_diagnostic, container, false);
view.findViewById(R.id.button_refresh_bluetooth_device).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        updateBluetoothDevice();
        view.findViewById(R.id.bluetooth_device_title).requestFocus();
    }
});
view.findViewById(R.id.button_refresh_network).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        updateNetworkSection();
        view.findViewById(R.id.android_network_title).requestFocus();
    }
});

Simplified version of my layout:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/parentPanel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="@dimen/alert_dialog_padding_material"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/topPanel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:clipToPadding="false"
        android:paddingLeft="@dimen/alert_dialog_padding_material"
        android:paddingRight="@dimen/alert_dialog_padding_material"
        android:paddingBottom="@dimen/floating_action_button_margin">

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/button_refresh"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_alignParentRight="true"
            android:padding="@dimen/floating_action_button_margin"
            android:src="@drawable/ic_fab_refresh"
            android:contentDescription="@string/content_description_refresh"
            app:elevation="4dp"
            app:borderWidth="0dp"
            app:fabSize="mini"/>

        <TextView
            android:id="@+id/alertTitle"
            style="?android:attr/windowTitleStyle"
            android:singleLine="true"
            android:ellipsize="end"
            android:layout_toLeftOf="@id/button_refresh"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:paddingBottom="@dimen/floating_action_button_margin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/title_diagnostics"/>
    </RelativeLayout>

    <ScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:layout_weight="1"
        android:orientation="vertical"
        android:minHeight="48dp"
        android:paddingLeft="@dimen/alert_dialog_padding_material"
        android:paddingRight="@dimen/alert_dialog_padding_material"
        android:paddingBottom="@dimen/alert_dialog_padding_material">

        <TableLayout
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:clipChildren="false"
            android:shrinkColumns="1"
            android:stretchColumns="1,2">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical">

                <TextView
                    android:id="@+id/bluetooth_device_title"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textAppearance="?android:attr/textAppearanceLarge"
                    android:text="@string/title_bluetooth_device"
                    android:focusable="true"
                    android:focusableInTouchMode="true"/>

                <android.support.design.widget.FloatingActionButton
                    android:id="@+id/button_refresh_bluetooth_device"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="@dimen/floating_action_button_margin"
                    android:src="@drawable/ic_fab_refresh"
                    android:contentDescription="@string/content_description_refresh"
                    app:elevation="4dp"
                    app:borderWidth="0dp"
                    app:fabSize="mini"/>
            </LinearLayout>

            <TableRow>

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginRight="5dp"
                    android:layout_gravity="center_vertical"
                    android:id="@+id/bt_connection_status_image"
                    tools:src="@drawable/ic_check"
                    android:contentDescription="@string/content_description_diagnostic_status"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:layout_gravity="center_vertical"
                    android:text="@string/label_state"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="5dp"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:layout_gravity="center_vertical"
                    tools:text="Connected"
                    android:id="@+id/bt_connection_status"/>

            </TableRow>

            <LinearLayout
                android:id="@+id/android_network_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical">

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textAppearance="?android:attr/textAppearanceLarge"
                    android:text="@string/title_android_to_network"
                    android:focusable="true"
                    android:focusableInTouchMode="true"/>

                <android.support.design.widget.FloatingActionButton
                    android:id="@+id/button_refresh_network"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="@dimen/floating_action_button_margin"
                    android:src="@drawable/ic_fab_refresh"
                    android:contentDescription="@string/content_description_refresh"
                    app:elevation="4dp"
                    app:borderWidth="0dp"
                    app:fabSize="mini"/>
            </LinearLayout>
            <TableRow>

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginRight="5dp"
                    android:layout_gravity="center_vertical"
                    android:id="@+id/android_network_state_image"
                    tools:src="@drawable/ic_check"
                    android:contentDescription="@string/content_description_diagnostic_status"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:layout_gravity="center_vertical"
                    android:text="@string/label_state"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="5dp"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:layout_gravity="center_vertical"
                    tools:text="Connected"
                    android:id="@+id/android_network_state"/>

            </TableRow>
        </TableLayout>
    </ScrollView>
</LinearLayout>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜