Stop ScrollView from auto-scrolling to an EditText

2019-01-08 08:47发布

Seems to be a common problem without a great solution that I have found. Goal is to stop a ScrollView from auto-scrolling to an EditText (or any view for that matter) that has focus.

You have a bunch of views (Buttons, TextViews, etc) in an ScrollView, one of which is an EditText. Upon clicking say a Button within the ScrollView, the ScrollView scrolls down to the EditText (its off screen). This is not desired, as there are other elements that you don't want scrolled off the screen.

Now I can stop this from happening when the screen first shows by having other focusable elements in the ScrollView. However, the general problem still exists. The user scrolls down manually to the EditText, enters some numbers, then scrolls up to the top (EditText off screen now), they click a button in the ScrollView, and guess what? The ScrollView scrolls down to that darn EditText.

I'm thinking about extending the ScrollView and overriding some of the methods there like findFocusableViewInBounds, but I have a feeling I'll just be getting myself into more trouble.

Please help if you can.

I've played around with things like having an 0 height EditText at the top of my ScrollView, adding Next Focusable element properties to the other items in the ScrollView, etc. I suppose one "hack" might be to get the EditText to lose focus when the virtual or manual keyboard gets hidden or something.

20条回答
走好不送
2楼-- · 2019-01-08 09:03

My solution is below, to trace the source code and override some function to stop auto scrolling by focused item.

You can check if the focusedView is TextView or its child is TextView, by using focusedView.findViewById(R.id.textview_id_you_defined) != null or focusedView instanceof TextView == true.

public class StopAutoFocusScrollView extends ScrollView {

    private View focusedView;
    private ScrollMonitorListener listener;

    public interface ScrollMonitorListener {
        public boolean enableScroll(View view);
    }
    public StopAutoFocusScrollView(Context context) {
        super(context);
    }

    public StopAutoFocusScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public StopAutoFocusScrollView(Context context, AttributeSet attrs, 
           int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setScrollMonitorListener(ScrollMonitorListener listener) {
        this.listener = listener;
    }

    @Override
    public void requestChildFocus(View child, View focused) {
        focusedView = focused
        super.requestChildFocus(child, focused);
    }
    //flow : requestChildFocus -> scrollToChild -> scrollBy
    //Therefore, you can give listener to determine you want scroll to or not
    @Override
    public void scrollBy(int x, int y) {
        if (listener == null || listener.enableScroll(focusedView)) {
            super.scrollBy(x, y);
        }
    }
}
查看更多
爷、活的狠高调
3楼-- · 2019-01-08 09:05

We can write a custom ScrollView and override the onScrollChanged method and clear the focus from the focused view and optionally hide the keyboard.

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    View v = getFocusedChild();
    if (v != null) {
        InputMethodManager imm = (InputMethodManager) getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
        v.clearFocus();
    }
    super.onScrollChanged(l, t, oldl, oldt);
}
查看更多
贼婆χ
4楼-- · 2019-01-08 09:06

I had the same problem. There's one trick that I'm using to deal with this problem:

public void onClick(View v) {
    button.requestFocusFromTouch(); //prevents from loosing focus and scrolling view down
    ....
}
查看更多
疯言疯语
5楼-- · 2019-01-08 09:09

Create a custom ScrollView (create a class and have it extend HorizontalScrollView) and make a getter setter for scrollable. Then override computeScrollDeltaToGetChildRectOnScreen.

How it works: Every time android has an edit text or something in focus that is off screen it calls method computeScrollDeltaToGetChildRectOnScreen to bring it into view. If you Override it and return 0 when it is disabled than it will not scroll...

So you will have A custom scroll view like this:

    public class TrackableHorizontalScrollView extends HorizontalScrollView {


    // true if we can scroll (not locked)
    // false if we cannot scroll (locked)
    private boolean mScrollable = true;

    public TrackableHorizontalScrollView(Context context) {
        super(context);
    }

    public TrackableHorizontalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TrackableHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setScrollingEnabled(boolean enabled) {
        mScrollable = enabled;
    }

    public boolean isScrollable() {
        return mScrollable;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // if we can scroll pass the event to the superclass
                if (mScrollable) return super.onTouchEvent(ev);
                // only continue to handle the touch event if scrolling enabled
                return mScrollable; // mScrollable is always false at this point
            default:
                return super.onTouchEvent(ev);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // Don't do anything with intercepted touch events if
        // we are not scrollable
        if (!mScrollable) return false;
        else return super.onInterceptTouchEvent(ev);
    }

    @Override
    public void scrollTo(int x, int y){
        if (!mScrollable) return;
        super.scrollTo(x, y);
    }


    //Custom smooth scroll method since norm is final and cannot be overridden
    public final void smooothScrollToIfEnabled(int x, int y){
         if (!mScrollable) return;
         smoothScrollTo(x, y);
    }

    @Override
    protected int computeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect rect){
        if (!mScrollable) return 0;
        return super.computeScrollDeltaToGetChildRectOnScreen(rect);
    }


}

You can use this inside your XML like this:

<com.your.package.ui.widget.TrackableHorizontalScrollView
            android:id="@+id/wi_et_credit_scroller"
            android:layout_toRightOf="@id/wi_et_credit_iv"
            android:layout_width="fill_parent"
            android:scrollbars="none"
            android:layout_height="wrap_content"
            android:paddingRight="5dp"
            android:layout_gravity="center_vertical">

<!--Whatever you have inside the scrollview-->

</com.your.package.ui.widget.TrackableHorizontalScrollView>
查看更多
\"骚年 ilove
6楼-- · 2019-01-08 09:11

After struggling with that problem for quite some time, I've found a solution that seems to work without being too ugly. First, make sure that whatever ViewGroup (directly) contains your EditText has descendantFocusability set to "Before Descendants," focusable set to "true" and focusableInTouchMode set to "true." This will not be the ScrollView itself, but the layout inside where you have your various views. Next add an onTouchListener to your ScrollView that removes focus from the EditText whenever it is touched, like so:

ScrollView scroll = (ScrollView)findViewById(R.id.scrollView1);
scroll.setOnTouchListener(new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (myEditText.hasFocus()) {
            myEditText.clearFocus();
        }
        return false;
    }
});

Tell me if that doesn't fix it. What should happen is that the Layout gets focus instead of the EditText, so no scrolling should happen.

查看更多
Viruses.
7楼-- · 2019-01-08 09:11

Here is what I did

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" style="@style/measurementTableRowStyle"
    android:focusable="true" android:layout_height="fill_parent">
    <requestFocus></requestFocus>
    <LinearLayout android:id="@+id/linearLayout1"
        android:orientation="horizontal" android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <TextView android:id="@+id/desc_text" android:text="Value : "
            style="@style/attributeNameTextStyle" android:layout_width="wrap_content"
            android:focusable="true" android:layout_height="wrap_content">
            <requestFocus></requestFocus>
        </TextView>

        <TextView style="@style/attributeValueStyle" android:id="@+id/value_text"
            android:text="TextView" android:layout_width="wrap_content"
            android:layout_height="wrap_content"></TextView>
    </LinearLayout>

The reason is in such cases you have to make all other views focus-able inside the scrollview by an explicit android:focusable="true" and then <requestFocus></requestFocus> . This should work everytime IMO

查看更多
登录 后发表回答