How to track the position of items while scrolling in a long custom listview
Asked Answered
T

2

0

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!

Terpene answered 17/3, 2014 at 9:48 Comment(0)
Y
1

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.

Yoghurt answered 19/3, 2014 at 19:40 Comment(7)
Your solution seems to work & position value seems to be increasing but 'position' also increases by 'TYPE_SEPERATOR' i.e. header & i want to minus the position for every header to display the correct data ! i tried doing -1 as u can see in alertdialog but it displays the correct data under 1st header ! I have many headers in my list & for that wht sh'd be done?Terpene
@Warde Well, perhaps you can keep a count variable to track number of TYPE_ITEM? Initialize this variable to zero and increment it only when type == TYPE_ITEM. Now you can use this variable in your AlertDialog. BUT - I still don't understand what you're doing here. So, if you can explain that, we could find a better approach to this.Yoghurt
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(indexing) of TYPE_ITEM for getting data from database. I hope u now understand my problem ! & BTW I tried using a count variable in and increment it only when type==TYPE_ITEM but what if we again scroll up(in that case the value should decrement). i hope u understand the problem now & i think we should implement notifyDataSetChanged or onScroll, onScrollStateChanged methods.Terpene
I edited my question by ur answer & added the clarification of questionTerpene
@Warde The suggestion to keep a count variable was terrible. Apologies. Why don't you create a wrapper class that handles both the item data and item description. In case of TYPE_SEPARATOR, you can pass null or empty string "" for description - it doesn't matter because we're not setting an OnClickListener for TYPE_SEPARATOR. I'm adding an edit to my answer. See if implementing a wrapper solves your problem.Yoghurt
now i want to combine this in my App with fragments. can u pls guide how to use it in a fragment ! I have posted a new question against this #22593703Terpene
@Warde Sorry, I was busy with exams. I see that user Raghunandan has addressed your concerns. :)Yoghurt
E
0

You can detect the scroll position easily and smooth by putting onScrollListener to your ListvView in your Acvivity. That contains two basic methods: onScroll and onScrollStateChanged.

EDIT

OnScrollListener has wide range of usage. This is OnScrollListener, which detects end of scroll and loads more data. Popular load-more feature.

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



                    }

                });

If you want to get the scroll position use onScrollStateChanged method. The int scrollState in code below is actually what you want. You can do whatever you want with it.

BTW This was just a example of usage of OnScrollListener but what you have to do is just set it without inner setting (in my case load more feature) but just use onScrollStateChanged and its int.

hope it helps!

Excalibur answered 17/3, 2014 at 10:26 Comment(3)
Can u provide more details on onScroll and onScrollStateChanged &how to use them ?Terpene
lv doesnt have any impact in my code because i am using Custom ListviewTerpene
so if your custom ListView extends ListView , you can set OnScrollListener also thereExcalibur

© 2022 - 2024 — McMap. All rights reserved.