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 (Button
s, TextView
s, 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.
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.
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
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
....
}
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>
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.
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
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;
}
}
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;
}
});
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>
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 .
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;
}
});
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);
}
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
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.
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" >
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;
}
}
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);
}
}
}
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>
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;
}
});
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);
}
});
...
}
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.