问题
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!
回答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.
回答2:
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!
来源:https://stackoverflow.com/questions/22451081/how-to-track-the-position-of-items-while-scrolling-in-a-long-custom-listview