web-dev-qa-db-fra.com

Android obtenir la position de défilement exacte dans ListView

Je voudrais obtenir la position exacte en pixels du défilement ListView. Et non, je ne parle pas de la première position visible.

Existe-t-il un moyen d'y parvenir?

38
saarraz1

D'accord, j'ai trouvé une solution de contournement, en utilisant le code suivant:

View c = listview.getChildAt(0);
int scrolly = -c.getTop() + listview.getFirstVisiblePosition() * c.getHeight();

La façon dont cela fonctionne est qu'il prend le décalage réel du premier élément de liste visible et calcule la distance qui le sépare du haut de la vue pour déterminer à quel point nous sommes "déroulés" dans la vue, maintenant que nous savons que nous pouvons calculer le reste utilisant la méthode régulière getFirstVisiblePosition.

79
saarraz1

La réponse de Saarraz1 ne fonctionnera que si toutes les lignes de la liste sont de la même hauteur et qu'il n'y a pas d'en-tête (ou c'est aussi la même hauteur que les lignes).

Notez qu'une fois que les lignes disparaissent en haut de l'écran, vous n'y avez pas accès, car vous ne pourrez pas suivre leur hauteur. C'est pourquoi vous devez enregistrer ces hauteurs (ou les hauteurs cumulées de tous). Ma solution nécessite de conserver un Dictionnaire des hauteurs par index (on suppose que lorsque la liste est affichée la première fois, elle défile vers le haut).

private Dictionary<Integer, Integer> listViewItemHeights = new Hashtable<Integer, Integer>();

private int getScroll() {
    View c = listView.getChildAt(0); //this is the first visible row
    int scrollY = -c.getTop();
    listViewItemHeights.put(listView.getFirstVisiblePosition(), c.getHeight());
    for (int i = 0; i < listView.getFirstVisiblePosition(); ++i) {
        if (listViewItemHeights.get(i) != null) // (this is a sanity check)
            scrollY += listViewItemHeights.get(i); //add all heights of the views that are gone
    }
    return scrollY;
}
29
Maria

L'idée la plus simple que j'ai pu trouver était d'étendre ListView et d'exposer le "computeVerticalScrollOffset" qui est protégé par défaut, puis d'utiliser "com.your.package.CustomListView" dans vos présentations XML.

public class CustomListView extends ListView {

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

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

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

    @Override
    public int computeVerticalScrollOffset() {
        return super.computeVerticalScrollOffset();
    }
}
20
jaredpetker

Si quelqu'un d'autre l'a trouvé dans Google en cherchant un moyen de suivre les décalages relatifs de défilement dans un OnScrollListener - c'est-à-dire, changer Y depuis le dernier appel à l'auditeur - voici un Gist montrant comment calculer cela .

9
cnnr

Déclarez d'abord votre variable int pour maintenir la position.

int position = 0;

puis ajoutez scrollListener à votre ListView,

listView.setOnScrollListener(new OnScrollListener() {

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // TODO Auto-generated method stub

        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
             position = firstVisibleItem;

        }
    });

Ensuite, après avoir obtenu de nouvelles données ou tout changement dans vos données, vous devez définir la position actuelle de la vue de liste

listView.setSelection(position);

J'ai utilisé après avoir configuré mon adaptateur, fonctionne très bien pour moi ..

6
CrazyMind

Je sais que je suis en retard à la fête mais j'avais envie de partager ma solution à ce problème. J'ai un ListView et j'essayais de trouver combien j'ai fait défiler pour faire défiler quelque chose d'autre par rapport à lui et provoquer un effet de parallaxe. Voici ma solution:

public abstract class OnScrollPositionChangedListener implements AbsListView.OnScrollListener {
    int pos;
    int prevIndex;
    int prevViewPos;
    int prevViewHeight;
    @Override
    public void onScroll(AbsListView v, int i, int vi, int n) {
        try {
            View currView = v.getChildAt(0);
            int currViewPos = Math.round(currView.getTop());
            int diffViewPos = prevViewPos - currViewPos;
            int currViewHeight = currView.getHeight();
            pos += diffViewPos;
            if (i > prevIndex) {
                pos += prevViewHeight;
            } else if (i < prevIndex) {
                pos -= currViewHeight;
            }
            prevIndex = i;
            prevViewPos = currViewPos;
            prevViewHeight = currViewHeight;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            onScrollPositionChanged(pos);
        }
    }
    @Override public void onScrollStateChanged(AbsListView absListView, int i) {}
    public abstract void onScrollPositionChanged(int scrollYPosition);
}

J'ai créé mon propre OnScrollListener où la méthode onScrollPositionChanged sera appelée à chaque appel de onScroll. Mais cette méthode aura accès à la valeur calculée représentant la quantité de défilement de ListView.

Pour utiliser cette classe, vous pouvez définirOnClickListener sur un nouveau OnScrollPositionChangedListener et remplacer la méthode onScrollPositionChanged.

Si vous devez utiliser la méthode onScroll pour d'autres choses, vous pouvez également remplacer cela, mais vous devez appeler super.onScroll pour que onScrollPositionChanged fonctionne correctement.

myListView.setOnScrollListener(
    new OnScrollPositionChangedListener() {
        @Override
        public void onScroll(AbsListView v, int i, int vi, int n) {
            super.onScroll(v, i, vi, n);
            //Do your onScroll stuff
        }
        @Override
        public void onScrollPositionChanged(int scrollYPosition) {
            //Enjoy having access to the amount the ListView has scrolled
        }
    }
);
2
aptaunk

J'avais rencontré le même problème, que je voulais placer la barre de recherche verticale à la valeur de défilement actuelle de ListView. J'ai donc ma propre solution comme celle-ci.

Première classe de création

public abstract class OnScrollPositionChangedListener implements AbsListView.OnScrollListener {
    int pos;
    public  int viewHeight = 0;
    public  int listHeight = 0;

    @Override
    public void onScroll(AbsListView v, int i, int vi, int n) {
        try {
            if(viewHeight==0) {
                viewHeight = v.getChildAt(0).getHeight();
                listHeight = v.getHeight();

            }
            pos = viewHeight * i;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            onScrollPositionChanged(pos);
        }
    }
    @Override public void onScrollStateChanged(AbsListView absListView, int i) {}
    public abstract void onScrollPositionChanged(int scrollYPosition);
}

Ensuite, utilisez-le dans Activité principale comme ceci.

public class MainActivity extends AppCompatActivity {
    SeekBar seekBar;
    ListView listView;
    OnScrollPositionChangedListener onScrollPositionChangedListener = new OnScrollPositionChangedListener() {
        @Override
        public void onScroll(AbsListView v, int i, int vi, int n) {
            super.onScroll(v, i, vi, n);
            //Do your onScroll stuff
        }

        @Override
        public void onScrollPositionChanged(int scrollYPosition) {
            //Enjoy having access to the amount the ListView has scrolled

            seekBar.setProgress(scrollYPosition);


        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        seekBar = (SeekBar) findViewById(R.id.mySeekBar);
        listView = (ListView) findViewById(R.id.listView);
        final String[] values = new String[]{"Android List View",
                "Adapter implementation",
                "Simple List View In Android",
                "Create List View Android",
                "Android Example",

        };

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                Android.R.layout.simple_list_item_1, Android.R.id.text1, values);
        listView.setAdapter(adapter);
        listView.setOnScrollListener(onScrollPositionChangedListener);



        seekBar.postDelayed(new Runnable() {
            @Override
            public void run() {
                seekBar.setMax((onScrollPositionChangedListener.viewHeight * values.length) - onScrollPositionChangedListener.listHeight);
            }
        }, 1000);
        seekBar.setEnabled(false);

    }
}

dans l'application Gradle

compile 'com.h6ah4i.Android.widget.verticalseekbar:verticalseekbar:0.7.0'

en disposition XML

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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/activity_main"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:paddingBottom="@dimen/activity_vertical_margin"
    Android:paddingLeft="@dimen/activity_horizontal_margin"

    Android:paddingRight="@dimen/activity_horizontal_margin"
    Android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.centsol.charexamples.MainActivity">


    <ListView
        Android:id="@+id/listView"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:fadeScrollbars="false"


        Android:scrollbars="none"

        Android:layout_alignParentTop="true"

        Android:layout_alignParentLeft="true"
        Android:layout_alignParentStart="true">

    </ListView>
    <!-- This library requires pair of the VerticalSeekBar and VerticalSeekBarWrapper classes -->
    <com.h6ah4i.Android.widget.verticalseekbar.VerticalSeekBarWrapper
        Android:layout_width="wrap_content"

        Android:layout_alignParentRight="true"
        Android:layout_height="match_parent">
        <com.h6ah4i.Android.widget.verticalseekbar.VerticalSeekBar
            Android:id="@+id/mySeekBar"
            Android:layout_width="0dp"
            Android:progressDrawable="@drawable/progress"
            Android:thumb="@drawable/thumb"
            Android:layout_height="0dp"
            Android:splitTrack="false"

            app:seekBarRotation="CW90" /> <!-- Rotation: CW90 or CW270 -->
    </com.h6ah4i.Android.widget.verticalseekbar.VerticalSeekBarWrapper>
    <View
        Android:layout_width="1dp"
        Android:visibility="visible"
        Android:layout_alignParentRight="true"
        Android:layout_marginTop="16dp"
        Android:layout_marginRight="2.5dp"
        Android:layout_marginBottom="16dp"
        Android:background="@Android:color/black"
        Android:layout_height="match_parent" />
</RelativeLayout>

arrière-plan xml

    <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:Android="http://schemas.Android.com/apk/res/Android" >

    <solid Android:color="@Android:color/transparent"/>

</shape>

remplissez xml

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:Android="http://schemas.Android.com/apk/res/Android" >

<solid Android:color="@Android:color/transparent" />


</shape>

progrès xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:Android="http://schemas.Android.com/apk/res/Android" >

    <item
        Android:id="@Android:id/background"
        Android:drawable="@drawable/background"/>
    <item Android:id="@Android:id/progress">
        <clip Android:drawable="@drawable/fill" />
    </item>

</layer-list>

pouce xml

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:shape="oval" >


<solid Android:color="@Android:color/holo_red_dark" />
    <size
        Android:height="5dp"
        Android:width="5dp" />

</shape>
1
Ali Imran

en plus de @ jaredpetker réponse. ListView ne contient pas tous les éléments dans son défilement, vous devez donc utiliser uniquement la partie "visible" de la liste. Lorsque vous faites défiler vers le bas, les éléments supérieurs sont déplacés et poussés en tant que nouvelles vues d'élément. C'est de là que vient conversionView (ce n'est pas un élément vide à remplir, c'est un élément décalé qui est hors de la partie "visible" de la liste. Donc, vous devez savoir combien d'éléments étaient avant la partie visible, multipliez-les avec ListItemHeight et ajoutez headerHeight, comment vous pouvez obtenir un vrai décalage absolu dans le défilement. Si u n'est pas en-tête, la position 0 sera listItem, donc vous pouvez simplifier absoluteY += pos*listItemHeight;

public class CustomListView extends ListView {

private int listItemHeight = 140;
private int headerHeight = 200;

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

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

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

@Override
public int computeVerticalScrollOffset() {
    final int y = super.computeVerticalScrollOffset();
    int absoluteY = y;
    int pos = getFirstVisiblePosition();
    if(pos > 0){ 
       absoluteY += (pos-1)*listItemHeight+header‌​Height;
    }
    //use absoluteY
    return y;
}
1
El'