web-dev-qa-db-fra.com

Arrêtez ScrollView de défiler automatiquement vers un EditText

Semble être un problème commun sans une excellente solution que j'ai trouvée. L'objectif est d'empêcher une ScrollView de faire défiler automatiquement l'écran vers une EditText (ou n'importe quelle vue) ayant le focus.

Vous avez un tas d'opinions (Buttons, TextViews, etc.) dans une ScrollView, dont l'une est EditText. En cliquant sur un bouton dans la ScrollView, la ScrollView défilera jusqu'à la EditText (son écran est éteint). Ceci n'est pas souhaité, car il y a d'autres éléments que vous ne voulez pas voir défiler de l'écran. 

Maintenant, je peux empêcher que cela se produise lorsque l’écran s’affiche pour la première fois en ayant d’autres éléments focalisables dans la variable ScrollView. Cependant, le problème général existe toujours. L'utilisateur fait défiler manuellement la EditText, saisit des nombres, puis fait défiler vers le haut (EditText éteint l'écran maintenant), il clique sur un bouton de la ScrollView et devine quoi? La ScrollView défile jusqu'à cette darn EditText.

Je songe à étendre la variable ScrollView et à remplacer certaines des méthodes telles que findFocusableViewInBounds, mais j'ai le sentiment que je vais m'attirer davantage.

S'il vous plait aidez si vous le pouvez. 

J'ai joué avec des choses comme avoir une hauteur EditText de 0 en haut de ma ScrollView, ajouter des propriétés d'élément Next Focusable aux autres éléments de la ScrollView, etc. Je suppose qu'un "bidouillage" pourrait consister à obtenir le EditText de perdre le focus quand le clavier virtuel ou manuel est caché ou quelque chose.

40
Fraggle

Après avoir lutté avec ce problème pendant un certain temps, j'ai trouvé une solution qui semble fonctionner sans être trop laide. Premièrement, assurez-vous que tout ce que ViewGroup contient (directement) votre EditText a descendantFocusability défini sur "Avant les descendants," focusable défini sur "true" et focusableInTouchMode défini sur "true" Ce ne sera pas la ScrollView elle-même, mais la mise en page à l'intérieur de laquelle vous avez vos différentes vues. Ajoutez ensuite une onTouchListener à votre ScrollView qui supprime le focus de la EditText chaque fois que vous la touchez, comme ceci:

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;
    }
});

Dis-moi si ça ne résout pas le problème. Ce qui devrait arriver, c’est que la disposition obtienne le focus au lieu de la EditText, de sorte qu’il ne devrait pas y avoir de défilement.

36
thomas88wp

Il suffit de créer une vue vide en haut de linéaire

<View Android:layout_width="fill_parent" Android:id="@+id/focus_view" Android:layout_height="0dp" Android:focusable="true" Android:focusableInTouchMode="true"><requestFocus/></View>

Une seule ligne résout le problème

19
KeT4yn

J'ai eu le même problème. Il y a une astuce que j'utilise pour résoudre ce problème:

public void onClick(View v) {
    button.requestFocusFromTouch(); //prevents from loosing focus and scrolling view down
    ....
}
9
pawelzieba

Le problème ne concerne pas le code Java, mais le code manifeste.

Dans votre AndroidManifest.xml, ajoutez un attribut à l'activité:

        <activity Android:name=".MyActivity" Android:windowSoftInputMode="adjustPan"> </activity>
8
Corbella

En ajoutant 2 paramètres dans:

Android:focusable="true"
Android:focusableInTouchMode="true"

Dans quelle disposition principale est là.

<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:id="@+id/layMain"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:background="@color/background"
    Android:focusable="true"
    Android:focusableInTouchMode="true">

Par cette EditText ne sera pas mis au point automatiquement.

6
Rohan Mistry

Voici ce que j'ai fait

 <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>

La raison en est que, dans de tels cas, vous devez rendre toutes les autres vues compatibles avec la vue de défilement à l'aide d'un Android:focusable="true" explicite, puis d'un <requestFocus></requestFocus>. Cela devrait fonctionner à chaque fois, IMO

4
SaKet

Mon correctif à ce bogue le plus horrible, (il est à noter qu'il s'agit de pré API11 uniquement où ils ont modifié la méthode fling pour ne pas être stupide).

La vieille méthode fling trouve le prochain objectif à atteindre, ce qui n’est pas vraiment utile. Les autres versions de cette classe ne fonctionnent pas vraiment car elles arrêtent le focus lorsque l'utilisateur traverse réellement le formulaire à partir du clavier.

public class NonFocusingScrollView extends ScrollView {

    private boolean mBlockRequestFocusOnFling = false;

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

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

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

    @Override
    public ArrayList<View> getFocusables(int direction) {
        if(mBlockRequestFocusOnFling)
            return new ArrayList<View>();
        return super.getFocusables(direction);
    }

    @Override
    public void requestChildFocus(View child, View focused) {
        if(!mBlockRequestFocusOnFling)
        super.requestChildFocus(child, focused);
    }


    @Override
    public void fling(int velocityY) {
        mBlockRequestFocusOnFling = true;
        super.fling(velocityY);
        mBlockRequestFocusOnFling = false;
    }
}
2
Chris.Jenkins

thomas88wp answer, https://stackoverflow.com/a/6486348/528746 a travaillé pour moi . Mais j'ai eu deux problèmes: 1. En faisant défiler, je voulais cacher le clavier
2. J'avais beaucoup de vues EditText et je ne voulais pas l'écrire pour chacune d'elles
(Je suis getActivity () puisque j'écris ceci dans un fragment et non une activité)

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

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // Check if the view with focus is EditText
            if (getActivity().getCurrentFocus() instanceof EditText)
            {
                EditText ed = (EditText)getActivity().getCurrentFocus();
                if (ed.hasFocus()) {

                    // Hide the keyboard
                    InputMethodManager inputManager = (InputMethodManager)
                            getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); 
                    inputManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(),
                            InputMethodManager.HIDE_NOT_ALWAYS);
                    // Clear the focus
                    ed.clearFocus();
                }
            }
            return false;
        }

    });
2
andydev

Créez une ScrollView personnalisée (créez une classe et faites-la étendre à HorizontalScrollView) et créez un getter-setter pour le défilement. Puis substituez computeScrollDeltaToGetChildRectOnScreen.

Comment ça marche: Chaque fois qu'Android a un texte édité ou un élément net hors de l'écran, il appelle la méthode computeScrollDeltaToGetChildRectOnScreen pour le visualiser. Si vous le remplacez et que vous retournez 0 lorsqu'il est désactivé, il ne défilera pas ...

Donc, vous aurez une vue de défilement personnalisée comme ceci:

    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);
    }


}

Vous pouvez utiliser ceci dans votre XML comme ceci:

<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>
1
aV1rus

Seul ce code fonctionne pour moi:

public static void preventScrollViewFromScrollingToEdiText(ScrollView view) {
    view.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
    view.setFocusable(true);
    view.setFocusableInTouchMode(true);
    view.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            v.requestFocusFromTouch();
            return false;
        }
    });
}

Tous les crédits vont à cette réponse originale .

1
Oleksandr

Une autre version du code de thomas88wp:

ScrollView scroll = (ScrollView)getActivity().findViewById(R.id.scrollView_addNewBill);
    scroll.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(View arg0, MotionEvent arg1) {        
            View focussedView = getCurrentFocus(); 
            if( focussedView != null ) focussedView.clearFocus();
                
            return false;
    }
});
1
Adil Malik

Nous pouvons écrire un ScrollView personnalisé et remplacer la méthode onScrollChanged , effacer le focus de la vue focalisée et éventuellement masquer le clavier.

@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);
}
1
Prasad Korhale

J'ai fait un projet test pour expérimenter les différentes solutions si quelqu'un veut jouer avec. https://github.com/marchold/EditText-ErrorPopup-Scroll-View

1
Marc

J'avais un problème similaire et je l'ai finalement fait fonctionner. Ma vue de défilement contient une série de boutons personnalisés, suivis d'un EditText (qui a normalement le focus, mais je ne veux pas que ce soit le cas). Chaque fois que les boutons sont cliqués, la vue de défilement défile automatiquement vers le EditText sélectionné. Remplacer public boolean requestChildRectangleOnScreen(final View child, final Rect rectangle, final boolean immediate) et renvoyer toujours false (comportement par défaut de ViewGroup) a été l’essentiel. J'espère que cela vous aidera aussi.

1
dynamic_knight

J'ai résolu ce problème en ajoutant l'attribut descendantFocusability à l'objet ScrollView contenant LinearLayout, avec la valeur blockDescendants.

Par exemple:

<LinearLayout
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:orientation="vertical"
    Android:descendantFocusability="blocksDescendants" >
0
madx

Essaye celui-là :)

public class CustomHorizontalScrollView extends HorizontalScrollView {

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

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

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
    return super.onTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    return super.onInterceptTouchEvent(ev);
    }

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


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

    @Override
    protected int computeScrollDeltaToGetChildRectOnScreen(Android.graphics.Rect rect) {
/*  if (getContext() != null && getContext() instanceof Activity) {
        Activity activity = (Activity) getContext();
        if (!activity.isFinishing()) {
        View view = activity.getCurrentFocus();
        if (view != null) {
            if (view instanceof EditText) {
            return 0;
            }
        }
        }
    }
    return super.computeScrollDeltaToGetChildRectOnScreen(rect);

*/
return 0;
    }

}
0
Soo Chun Jung

Ma solution est ci-dessous, pour tracer le code source et remplacer une fonction pour arrêter le défilement automatique par élément sélectionné.

Vous pouvez vérifier si focusedView est TextView ou si son enfant est TextView, En utilisant focusedView.findViewById(R.id.textview_id_you_defined) != null ou 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);
        }
    }
}
0
Hsiao-Ting

La meilleure solution consiste à ajouter des options de focus pour l'enfant de votre scrollview: 

Android:descendantFocusability="beforeDescendants"
Android:focusable="true"
Android:focusableInTouchMode="true"

Ensuite, votre fichier XML ressemblera à ceci:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:id="@+id/scrollView"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent" >

    <LinearLayout
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:layout_marginRight="50dp"
        Android:descendantFocusability="beforeDescendants"
        Android:focusable="true"
        Android:focusableInTouchMode="true"
        Android:orientation="vertical" >

        <EditText
            Android:id="@+id/editText_one"
            Android:layout_width="match_parent"
            Android:layout_height="100dp"
            Android:text="TestApp 1" />

        <EditText
            Android:id="@+id/editText_two"
            Android:layout_width="match_parent"
            Android:layout_height="100dp"
            Android:text="TestApp 2" />

       <EditText
            Android:id="@+id/editText_three"
            Android:layout_width="match_parent"
            Android:layout_height="100dp"
            Android:text="TestApp 3" />


    </LinearLayout>

</ScrollView>
0
Aj 27

J'ai eu une objection légèrement différente à cette carence exaspérante. Chaque fois que j'ai tapé sur l'un des RadioButtons situés sous les EditTexts, la position de défilement a sauté pour s'adapter à ce qu'Android a déterminé être le EditText visible et concentré.

Toutes les tentatives visant à conserver la position de défilement souhaitée par l'intermédiaire d'une Runnable ayant émis ScrollView.scrollTo(x,y) ont été IGNORÉES par bonté!

Je partage ma solution dans l’espoir que cela puisse faire économiser à huit autres personnes huit (8) heures perdues. 

/* This interesting little 'hack' prevents unwanted scroll 'jump' occurring when
 user touches a RadioButton for example
[ Causes focus to change - but maybe this is a lesser evil! ] */
mScrollView.setOnTouchListener(new View.OnTouchListener() {
    @Override public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() != MotionEvent.ACTION_UP) 
            return false;

        mScrollView.clearFocus();
        return false;
    }
});
0
Bad Loser

J'ai souvent ce problème lorsque mes applications gèrent le changement d'orientation.

Dans ce cas, j'utilise le type de code suivant:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // to avoid the scrollview to scroll to this element automatically
    mEditTextSearch.setFocusable(false);
    // Get the saved scroll position
    final int scrolly = savedInstanceState.getInt("scrolly");
    mScrollView.post(new Runnable() {
        @Override
        public void run() {
            mScrollView.scrollTo(0, scrolly);
            // Restore the initial state of the EditText
            mEditTextSearch.setFocusable(true);
            mEditTextSearch.setFocusableInTouchMode(true);
            mEditTextSearch.setClickable(true);
        }
    });
    ...
}
0
ol_v_er

Pour moi, cela n'a pas fonctionné de remplacer ScrollView onTouch. De plus, Android ne fonctionnait pas: descendantFocusability = "beforeDescendants" Android: focusableInTouchMode = "true" Cette solution, ainsi que d’autres mentionnées précédemment, ne fonctionnaient que pour la première fois - uniquement si EditText n’était pas sélectionné, mais une fois sélectionné, scrollview autoscrolls à nouveau.

Comme j'avais déjà écrit un code pour cacher un clavier lorsque je touchais d'autres vues, j'ai simplement ajouté deux lignes de code et cela fonctionnait comme un champion:

public static void setupUI(final Activity activity, final View view) {
    //view is the parent view in your layout
    OnTouchListener mTouchListener = new OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            try {
                View vFocused = null;
                vFocused = activity.getCurrentFocus();

                if (vFocused != null) {
                    hideSoftKeyboard(activity, v);
                    if (vFocused instanceof EditText) {
                        vFocused.clearFocus();//this is the trick to avoid ScrollView autoscroll
                    }
                }
            } catch (Exception e) {
            }
            return false;
        }
    };

    // Set up touch listener for non-text box views to hide keyboard.
    if (!(view instanceof EditText) && !(view instanceof ViewGroup)) {
        view.setOnTouchListener(mTouchListener);
    }

    // If a layout container, iterate over children and seed recursion.
    if (view instanceof ViewGroup) {
        view.setOnTouchListener(mTouchListener);
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            View innerView = ((ViewGroup) view).getChildAt(i);
            setupUI(activity, innerView);
        }
    }
}
public static void hideSoftKeyboard(Context context, View v) {
    InputMethodManager inputMethodManager = (InputMethodManager) context
            .getSystemService(Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
}

a également ajouté ceci dans la vue racine:

Android:descendantFocusability="beforeDescendants" Android:focusableInTouchMode="true"

Peut-être que ce n'est pas vraiment une bonne solution, mais que ça fonctionne.

0