【问题标题】:How to track the position of items while scrolling in a long custom listview如何在长自定义列表视图中滚动时跟踪项目的位置
【发布时间】:2014-04-22 11:08:41
【问题描述】:

从数据库中以列表视图的形式显示的数据(这里列表视图的标题禁用了标题的onClick)。

我试图显示来自positiongetView() 的所选项目的描述。该列表非常大,因此它会在滚动时动态分配视图,而position 在滚动后会给出错误的值

我观看了Google I/O 2010 - The world of ListView 视频和it states these things

所以我想我需要实现 notifyDataSetChanged()onScroll()onScrollStateChanged() 方法。

但是怎么做呢?

代码:

public class MainActivity1 extends ListActivity implements OnTouchListener{

private MyCustomAdapter mAdapter;
Activity temp = this;
String []s = new String[500];
ArrayList<GS> q = new ArrayList<GS>();
CustomAdapter adapter;
ListView lv;
int c=1;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

 DBAdapter db = DBAdapter.getDBAdapter(getApplicationContext());
    if (!db.checkDatabase()) 
    {
        db.createDatabase(getApplicationContext());
    }
    db.openDatabase();

    q = db.getData();

    mAdapter = new MyCustomAdapter();
    mAdapter.addSeparatorItem(q.get(0).getA_name());
    mAdapter.addItem(q.get(0).getAS_name());
    for (int i = 1; i < 460; i++) {

        if (!(q.get(i).getA_name().trim().equals(q.get(i-1).getA_name().trim()))) {
            mAdapter.addSeparatorItem(q.get(i).getA_name());
            c++;
        }
        mAdapter.addItem(q.get(i).getAS_name());

    }

    setListAdapter(mAdapter);        

}
 //Adapter Class
 private class MyCustomAdapter extends BaseAdapter {

    private static final int TYPE_ITEM = 0;
    private static final int TYPE_SEPARATOR = 1;
    private static final int TYPE_MAX_COUNT = TYPE_SEPARATOR + 1;

    private ArrayList<String> mData = new ArrayList<String>();
    private LayoutInflater mInflater;

    private TreeSet<Integer> mSeparatorsSet = new TreeSet<Integer>();

    public MyCustomAdapter() {
        mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public void addItem(final String item) {
        mData.add(item);
        notifyDataSetChanged();
    }

    public void addSeparatorItem(final String item) {
        mData.add(item);
        // save separator position
        mSeparatorsSet.add(mData.size() - 1);
        notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        return mSeparatorsSet.contains(position) ? TYPE_SEPARATOR : TYPE_ITEM;
    }

    @Override
    public int getViewTypeCount() {
        return TYPE_MAX_COUNT;
    }

    public int getCount() {
        return mData.size();
    }

    public String getItem(int position) {
        return mData.get(position);
    }

    public long getItemId(int position) {
        Log.v("getItemId Position", ""+position);
        return position;

    }

            public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        int type = getItemViewType(position);
     //   System.out.println("getView " + position + " " + convertView + " type = " + type);
        if (convertView == null) {
            holder = new ViewHolder();
            switch (type) {
            case TYPE_ITEM:
                convertView = mInflater.inflate(R.layout.activity_main1, null);
                holder.textView = (TextView)convertView.findViewById(R.id.text);

                break;
            case TYPE_SEPARATOR:
                convertView = mInflater.inflate(R.layout.activity_main2, null);
                holder.textView = (TextView)convertView.findViewById(R.id.textSeparator);
                count++;
                break;
            }
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder)convertView.getTag();
        }
        holder.textView.setText(mData.get(position));

        // We set the OnClickListener here because it is unique to every
        // item. Although views can be recycled & reused, an OnClickListener cannot be.
        if (type == TYPE_ITEM) {
            holder.textView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        AlertDialog.Builder x = new AlertDialog.Builder(
                                temp);
                        Log.v("position",""+position);
                               x.setIcon(R.drawable.ic_launcher)
                                .setTitle(q.get(position-count).getAS_name())
                                .setMessage(q.get(position-count).getDesc_art())
                                .setCancelable(true)
                                .setPositiveButton("OK",
                                        new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface arg,
                                                    int arg1) {
                                            }
                                        });
                               AlertDialog a = x.create();
                        a.show();
                    }
                });
           } else {
            holder.textView.setOnClickListener(null);
            count++;
        }   

        return convertView;
    }
}

public static class ViewHolder {
    public TextView textView;
}

  public boolean onTouch(View v, MotionEvent event) {
 // TODO Auto-generated method stub
  return false;
 }
}

所以,我试图在单击TYPE_ITEM 时在 Alertdialog 中显示所选项目的描述。每个 TYPE_ITEM 的描述按顺序存储在数据库中。因此,我需要跟踪 TYPE_ITEM 的位置(意味着索引)以从数据库中获取数据。我希望你现在明白我的问题了!

顺便说一句,我尝试在getView() 中使用计数变量,但是如果我们再次向上滚动会怎样(在这种情况下,count 应该递减)。我希望你现在明白这个问题,我认为我们应该实现 notifyDataSetChanged 或 onScroll、onScrollStateChanged 方法。

如果问题不清楚,可以在cmets问我

请帮忙!

【问题讨论】:

    标签: android listview android-listview arraylist baseadapter


    【解决方案1】:

    您需要做的就是将OnClickListener 的设置移动到holder.textview

    这里的问题是:

    :由于您在if (convertView == null) 块中设置了 OnClickListener,因此它没有更改 当convertView 不为空。所以,AlertDialog 中使用的position 就是你设置的那个 当convertView 为空。

    : 解决方案是在每次处理一个位置时设置 OnClickListener - 我们不关心是否 视图是否被回收!我们需要重置 OnClickListener 以反映 正确/当前位置。

    :虽然,当 item 为 TYPE_SEPARATOR 时,我们从未在 holder.textview 上设置 OnClickListener,但它的 使用holder.textview.setOnClickListener(null) 安全地删除 OnClickListener。

    试试下面的更新代码。我希望这里的逻辑很清楚。

    //Adapter Class
    private class MyCustomAdapter extends BaseAdapter {
    
        ....
        ....     
        public View getView(final int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            int type = getItemViewType(position);
            System.out.println("getView " + position + " " + convertView + " type = " + type);
            if (convertView == null) {
                holder = new ViewHolder();
                switch (type) {
                case TYPE_ITEM:
                    convertView = mInflater.inflate(R.layout.activity_main1, null);
                    holder.textView = (TextView)convertView.findViewById(R.id.text);
                    break;
                case TYPE_SEPARATOR:
                    convertView = mInflater.inflate(R.layout.activity_main2, null);
                    holder.textView = (TextView)convertView.findViewById(R.id.textSeparator);
                    break;
                }
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.textView.setText(mData.get(position));
    
            // We set the OnClickListener here because it is unique to every
            // item. Although views can be recycled & reused, an OnClickListener cannot be.
            if (type == TYPE_ITEM) {
                holder.textView.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            AlertDialog.Builder x = new AlertDialog.Builder(
                                    temp);
                            Log.v("position",""+position);
                                   x.setIcon(R.drawable.ic_launcher)
                                    .setTitle(q.get(position-1).getAS_name())
                                    .setMessage(q.get(position-1).getDesc_art())
                                    .setCancelable(true)
                                    .setPositiveButton("OK",
                                            new DialogInterface.OnClickListener() {
                                                @Override
                                                public void onClick(DialogInterface arg,
                                                        int arg1) {
    
                                                }
                                            });
                            AlertDialog a = x.create();
                            a.show();
                        }
                  });
            } else {
                holder.textview.setOnClickListener(null);
            }   
    
            return convertView;
        }
    
        ....
        ....
    
    }
    

    编辑:

    包装类(可以作为MainActivity1的内部类实现或独立实现):

    public class ContentWrapper {
    
        private String mItem, mItemDescription;
    
        public ContentWrapper(String item, String itemDescription) {
            mItem = item;
            mItemDescription = itemDescription;
        }
    
        public String getItem() {
            return mItem;
        }
    
        public String getItemDescription() {
            return mItemDescription;
        }
    }
    

    您的数据设置也会改变:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        DBAdapter db = DBAdapter.getDBAdapter(getApplicationContext());
        if (!db.checkDatabase()) 
        {
            db.createDatabase(getApplicationContext());
        }
        db.openDatabase();
    
        q = db.getData();
    
        mAdapter = new MyCustomAdapter();
    
        // mAdapter.addSeparatorItem(q.get(0).getA_name());
    
        // First separator item
        // No description
        mAdapter.addSeparatorItem(new ContentWrapper(q.get(0).getA_name(), null));
    
        // mAdapter.addItem(q.get(0).getAS_name());
    
        // First TYPE_ITEM
        // Pass the description
        mAdapter.addItem(new ContentWrapper(q.get(0).getAS_name(), q.get(0).getDesc_art()));
    
    
        for (int i = 1; i < 460; i++) {
    
            if (!(q.get(i).getA_name().trim().equals(q.get(i-1).getA_name().trim()))) {
                // mAdapter.addSeparatorItem(q.get(i).getA_name());
                mAdapter.addSeparatorItem(new ContentWrapper(q.get(i).getA_name(), null));
                c++;
            }
    
            // mAdapter.addItem(q.get(i).getAS_name());
            mAdapter.addItem(new ContentWrapper(q.get(i).getAS_name(), q.get(i).getDesc_art()));
        }
    
        setListAdapter(mAdapter);        
    }
    

    接下来,我们对适配器进行修改:

    // private ArrayList<String> mData = new ArrayList<String>();
    private ArrayList<ContentWrapper> mData = new ArrayList<ContentWrapper>();
    

    add* 方法

    public void addItem(ContentWrapper value) {
        mData.add(value);
        notifyDataSetChanged();
    }
    
    public void addSeparatorItem(ContentWrapper value) {
        mData.add(value);
        // save separator position
        mSeparatorsSet.add(mData.size() - 1);
        notifyDataSetChanged();
    }
    
    public ContentWrapper getItem(int position) {
        return mData.get(position);
    }
    

    getView(...) 方法:

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ....
        ....
    
        holder.textView.setText(mData.get(position).getItem());
    
        if (type == TYPE_ITEM) {
            holder.textView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    AlertDialog.Builder x = new AlertDialog.Builder(temp);
                    Log.v("position",""+position);
                           x.setIcon(R.drawable.ic_launcher)
    
                            // .setTitle(q.get(position-count).getAS_name())
                            .setTitle(mData.get(position).getItem())
    
                            // .setMessage(q.get(position-count).getDesc_art())
                            .setMessage(mData.get(position).getItemDescription())
    
                            .setCancelable(true)
                            .setPositiveButton("OK",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface arg,
                                                int arg1) {
                                        }
                                    });
                     AlertDialog a = x.create();
                     a.show();
                 }
             });
        } else {
            holder.textView.setOnClickListener(null);
        }
    }
    

    就是这样。

    [我]认为我们应该实现 notifyDataSetChanged 或 onScroll, onScrollStateChanged 方法。

    notifyDataSetChanged() 用于告诉适配器底层数据已更改,因此需要刷新。例如,如果某个项目的描述发生更改,您将在 mData 中更新该项目并调用 notifyDataSetChanged()。但是在您的情况下(以及您的代码告诉我的内容),您的数据在使用 -setListAdapter(mAdapter) 设置适配器之前被初始化。因此,甚至不需要在 add* 方法中调用 notifyDataSetChanged()。在将适配器附加到列表视图之前调用 notifyDataSetChanged() 没有任何作用。

    onScrollonScrollChanged 用于不同的目的。例如,假设您在用户滚动超过第 50 个项目时显示Go To Top of the List 按钮 - 并在他们向上滚动第 50 个位置时隐藏它。在您的情况下,问题在于您试图从两个不同的来源(mData,q)获取数据并且存在同步问题。没有别的了。

    【讨论】:

    • 您的解决方案似乎有效,position 值似乎在增加,但“位置”也增加了“TYPE_SEPERATOR”,即标题&我想减去每个标题的位置以显示正确的数据!我尝试做-1,正如您在 alertdialog 中看到的那样,但它在第一个标题下显示正确的数据!我的列表中有很多标题,为此应该做些什么?
    • @Warde 好吧,也许您可​​以保留一个count 变量来跟踪TYPE_ITEM 的编号?将此变量初始化为zero,并在type == TYPE_ITEM将其递增。现在您可以在AlertDialog 中使用此变量。 但是 - 我还是不明白你在这里做什么。所以,如果你能解释一下,我们就能找到更好的方法。
    • 我试图在点击TYPE_ITEM时在Alertdialog中显示所选项目的描述。每个TYPE_ITEM的描述按顺序存储在数据库中。因此,我需要跟踪TYPE_ITEM 的位置(索引)以从数据库中获取数据。我希望你现在明白我的问题了! &顺便说一句,我尝试使用计数变量并仅在 type==TYPE_ITEM 时才增加它,但是如果我们再次向上滚动(在这种情况下,值应该减少)。我希望你现在明白这个问题&我认为我们应该实现 notifyDataSetChanged 或 onScroll、onScrollStateChanged 方法。
    • 我通过您的回答编辑了我的问题并添加了问题的说明
    • @Warde 保留count 变量的建议很糟糕。道歉。为什么不创建一个同时处理item dataitem description 的包装类。如果是TYPE_SEPARATOR,您可以通过nullempty string "" 进行描述 - 没关系,因为我们没有为TYPE_SEPARATOR 设置OnClickListener。我正在为我的答案添加一个编辑。看看实现包装器是否能解决您的问题。
    【解决方案2】:

    您可以通过将 onScrollListener 放入 Acvivity 中的 ListvView 轻松流畅地检测滚动位置。其中包含两个基本方法:onScroll 和 onScrollStateChanged。

    编辑

    OnScrollListener 用途广泛。这是 OnScrollListener,它检测滚动结束并加载更多数据。流行的加载更多功能。

    mListView.setOnScrollListener(new OnScrollListener() {
    
                        @Override
                        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                                int totalItemCount) {
                            if(mListView.getRefreshableView().getCount()!=0&&mListView.getRefreshableView().getCount()>0&&mAdapter.getCount()!=0){
                            if (mListView.getRefreshableView().getLastVisiblePosition() == mListView.getRefreshableView().getAdapter().getCount() - 1
                                    && mListView.getRefreshableView()
                                    .getChildAt(mListView.getRefreshableView().getChildCount() - 1)
                                    .getBottom() <= mListView.getRefreshableView().getHeight()) {
    
    
                                if(SplashScreen.countie  == mAdapter.getCount()){
                                    if(footie.isShown()) {
                                    mListView.getRefreshableView().removeFooterView(footie);    
                                    }
                                }
    
                                else{
                                    if(loading!=true&&dontupdate==false){
    
    
    
                                        updateMoreData();
                                        }
    
                                        else{}
                                }
                            }
                        }
    
                        }
                        @Override
                        public void onScrollStateChanged(AbsListView view,
                                int scrollState) {
                            //the int scrollState is what are you looking for                           
    
                            if (SCROLL_STATE_TOUCH_SCROLL == scrollState) {
                                View currentFocus = getActivity().getCurrentFocus();
                                if(currentFocus != null) {
                                    currentFocus.clearFocus();
                                }
                            }
    
    
    
                        }
    
                    });
    

    如果要获取滚动位置,请使用onScrollStateChanged 方法。下面代码中的 int scrollState 实际上就是您想要的。你可以用它做任何你想做的事。

    顺便说一句 这只是OnScrollListener 用法的一个示例,但您只需设置它而不进行内部设置(在我的情况下加载更多功能),而只需使用onScrollStateChanged 及其int

    希望对你有帮助!

    【讨论】:

    • 你能提供更多关于 onScroll 和 onScrollStateChanged 的​​详细信息以及如何使用它们吗?
    • lv 对我的代码没有任何影响,因为我使用的是自定义列表视图
    • 所以如果你的自定义 ListView 扩展 ListView ,你也可以在那里设置 OnScrollListener
    猜你喜欢
    • 2015-05-01
    • 2014-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多