如何获取ListView中行的位置(在屏幕上)

7

我已经实现了一些手势检测代码,以便在我的ListView(位于FrameLayout中)中检测到行被划过的情况。

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
        int itemId = MainActivity.this.getListView().pointToPosition((int) e1.getX(),(int) e1.getY());
        Offer order = (Offer)MainActivity.this.getListView().getAdapter().getItem(itemId);
        View v = MainActivity.this.getListView().getChildAt(itemId);
    }
}

我希望在滑动的行上方显示一个视图,并为该行提供一组上下文敏感的选项。

我的问题是以下方法:

v.getTop()
v.getBottom()

在视图被滚动之前,只返回正确的值。我可能可以进行一些计算,使用滚动位置等来计算偏移量,但我还注意到,只有在我向左滑动初始可见屏幕上的行时,才能获得这些值。如果我向下滚动列表,然后滑动一行(最初不在屏幕外),则这些方法返回空值。

以下方法似乎遇到了同样的问题...

v.getLocationOnScreen(loc)
v.getLocationInWindow(loc)

最终,我想找到可见listView顶部和被滑动的行之间的距离。然后,我将使用此距离将一个视图添加到父FrameLayout中,并添加适当的高度填充(以覆盖已滑动的行)。

非常感谢您的帮助!

3个回答

17

ListView有两种不同的项目索引方式,一种是通过在适配器中的位置索引,另一种是通过在屏幕上显示的位置索引。 除非您更改列表,否则项目始终位于适配器中的相同位置。 view.getChildAt()中的索引将根据屏幕上可见的内容而异。

pointToPosition返回的位置是适配器中的位置。 如果要获取物理视图,需要考虑视图是否滚动。 请尝试以下操作:

int adapterIndex = MainActivity.this.getListView().pointToPosition((int) e1.getX(),(int) e1.getY());
int firstViewItemIndex = MainActivity.this.getListView().getFirstVisiblePosition();
int viewIndex = adapterIndex - firstViewItemIndex;
View v = MainActivity.this.getListView().getChildAt(viewIndex);

你能详细解释一下在运行时如何动态地获取被滑动的行吗? - Cheryl Simon
使用GestureDetector和GestureListener在ListView上。我已经更新了上面的代码片段,包括onFling方法(和参数),当用户在列表上滑动手指时调用该方法。一些简单的数学运算用于检查用户是否从左到右滑动,然后使用pointToPosition找到滑动对应的行。 - Damian
1
好的... PointToRowId和PointToPosition都能返回被滑过的行的正确ID(列表中的序列号)。然而,我的后续调用“View v = MainActivity.this.getListView().getChildAt(itemId);”在引用一个最初屏幕外或超出Nexus One 800像素高度的itemId时返回null。 - Damian
好的,抱歉,我认为你是正确的。它取决于您Adapter实现中getItemId的实现方式。文档说它应该接受“position”并返回“rowId”,但在实践中,它通常只返回position。我认为关于适配器与listView中索引的观点仍然是正确的。请参见我上面的编辑。 - Cheryl Simon
谢谢。问题是,我正在尝试分别对每一行进行动画处理,但它们在被滑动时会依次进行动画处理。所以,首先是第1行,然后是第1行和第2行,接着是第1行、第2行和第3行同时进行动画处理。能否提供一点帮助? - worked
显示剩余3条评论

1

我已经解决了这个问题,但感觉有点像是一个hack,而且可能不是最有效的实现方式。肯定有一种更干净的方法来使用Android APIs完成这个任务吧?

在下面的代码中,我向我的ArrayAdaptor添加了一个HashMap,以跟踪哪些屏幕视图正在持有哪些行(因为在滚动列表时这些视图会被回收)

我对原始适配器所做的所有更改都已在下面进行了注释。

 class RestaurantAdapter extends ArrayAdapter<Offer> {
            Map hash = null;  //HashMap to keep track of rowId and on screen View

            RestaurantAdapter() {
                super(MainActivity.this, android.R.layout.simple_list_item_1, model);
                hash = new HashMap(); //instantiate HashMap in constructor
            }

             //method to return correct View on screen for row id
            public View getViewOnScreen(int rowId) {
                return (View)hash.get(rowId);
            }

            public View getView(int position, View convertView,
                                                    ViewGroup parent) {
                View row=convertView;
                hash.put(position, convertView); // add/update view for row position as it is recycled 

                RestaurantWrapper wrapper=null;

                if (row==null) {                                                    
                    LayoutInflater inflater=getLayoutInflater();

                    row=inflater.inflate(R.layout.row, parent, false);
                    wrapper=new RestaurantWrapper(row);
                    row.setTag(wrapper);
                }
                else {
                    wrapper=(RestaurantWrapper)row.getTag();
                }

                wrapper.populateFrom(model.get(position));

                return(row);
            }


        }

我尝试了这个解决方案,但对我没有用。我无法修改返回的视图。不过,在我的情况下,使用getChildAt在列表上确实有效。我不确定为什么我不能使用这种方法修改返回的视图。 - Andy
实际上,getChildAt对我来说同样没有起效,就像它对你也没有起效一样,所以我最终使用了这个页面上的另一个答案,结果行得通了。 - Andy

1

这是我发现的最佳方法来检测哪一行被触摸。

            Rect rect = new Rect();
            int childCount = mListView.getChildCount();
            int[] listViewCoords = new int[2];
            mListView.getLocationOnScreen(listViewCoords);
            int listX = (int) event.getRawX() - listViewCoords[0];
            int listY = (int) event.getRawY() - listViewCoords[1];

            View child;
            for (int i = 0; i < childCount; i++) {
                child = mListView.getChildAt(i);
                child.getHitRect(rect);
                if (rect.contains(listX, listY)) {
                    mDownView = child;
                    break;
                }
            }

你可以在ACTION_DOWN事件的onTouch方法中使用它。问候。

谢谢。然而,getChildAt() 方法确实返回第一个 可见 子元素(而不是列表中的第一个子元素)。为了让它正常工作,您可以使用以下代码:for(int i = 0 ; i < mListView.getLastVisiblePosition() - mListView.getFirstVisiblePosition() ; ++i)这样,被触摸的元素就位于 "i + mListView.getFirstVisiblePosition" 的位置上。 - Zach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接