Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
728 views
in Technique[技术] by (71.8m points)

android - How to track the position of items while scrolling in a long custom listview

The data which is displayed from the database in the form of listview (here listview with headers having disabled onClick of headers).

I tried to display the description of the selected item from position of getView(). The list is very large so it dynamically allocates the view while scrolling & the position gives wrong values after scrolling

I watched Google I/O 2010 - The world of ListView video & it states these things.

So I think I need to implement notifyDataSetChanged() or onScroll() , onScrollStateChanged() methods.

But how?

Code:

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

So,I am trying to display the description of the selected item in Alertdialog when TYPE_ITEM is clicked.The description of each TYPE_ITEM is stored in the database sequentially. Hence, I need to keep track the position(means indexing) of TYPE_ITEM for getting data from database. I hope u now understand my problem !

BTW I tried using a count variable in getView() that but in that what if we again scroll up(in that case the count should decrement). i hope u understand the problem now & i think we should implement notifyDataSetChanged or onScroll, onScrollStateChanged methods.

If the problem is not clear then u can ask me in the comments

Pls help!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

All you need to do is move the setting of OnClickListener on holder.textview.

The problem here is:

: Since you set the OnClickListener in the if (convertView == null) block, it isn't changed when convertView is not null. So, the position used in AlertDialog is the one you set when convertView was null.

: Solution is to set the OnClickListener every time a position is processed - we don't care if the view is recycled or not!!! We need to reset the OnClickListener to reflect the correct/current position.

: Although, we never set an OnClickListener on holder.textview when item is TYPE_SEPARATOR, its safe to remove the OnClickListener using holder.textview.setOnClickListener(null).

Try the updated code below. I hope the logic here is quite clear.

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

    ....
    ....

}

Edit:

Wrapper class (can be implemented as an inner class of MainActivity1 or independently):

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

Your data-setup will also change:

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

Next, we make changes to the adapter:

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

The add* methods

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

The getView(...) method:

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

And that's about it.

[I] think we should implement notifyDataSetChanged or onScroll, onScrollStateChanged methods.

notifyDataSetChanged() is used to tell the adapter that the underlying data has changed and thus a refresh is required. For example, if description for an item changes, you would update that item in mData and call notifyDataSetChanged(). But in your case (and from what your code tells me), your data is initialized before you set the adapter using - setListAdapter(mAdapter). So, calls to notifyDataSetChanged() inside the add* methods are not even required. Calling notifyDataSetChanged() before attaching an adapter to a listview does nothing.

onScroll and onScrollChanged are meant for a different purpose. For example, say that you show a Go To Top of the List button when the user scroll past the 50th item - and hide it when they scroll up the 50th position. In your case, the problem was that you were trying to get data from two different sources(mData, q) and there were issues with synchronization. Nothing else.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...