Focus Added Item in ExpandableListView - android

This seems simple but for the life of me I can't figure this out.
I have an ExpandableListView with a customized ExpandableListAdapter descended from BaseExpandableListAdapter. When I add a new item I simply want to focus the new item. I can't get that to work.
I tried the following, which does not work. This is in the activity with the listview:
private void processNewItem(C360ItemOwner itemOwner, int pos){
Item item = itemOwner.newItem(pos);
expAdapter.notifyDatasetChanged();
expandList.requestFocus();
if (item.getParentType() == Item.PARENT_IS_CHECKLIST) {//main level
expandList.setSelectedGroup(pos);
} else //Item.PARENT_IS_ITEM //sub level
expandList.setSelectedChild(item.getParentAsItem().getPosition(), pos, true);
}
I tried doing it in the adapter on the getView method, which does not work:
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
ViewGroup parent) {
if (convertView == null)
convertView = inf.inflate(R.layout.edit_group_item, null);
Item group = (Item) getGroup(groupPosition);
convertView.setTag(group);
EditText edtParent = (EditText) convertView.findViewById(R.id.edtParent);
edtParent.setTag(convertView);
scatterItemText(edtParent);
if (group.isNew()) {
Toast.makeText(edtParent.getContext(), "Found Focus Item " + groupPosition, Toast.LENGTH_SHORT).show();
edtParent.requestFocus();
}
return convertView;
}
I tried setting up a couple of fields in the adapter: focusItem, a write only Item object and focusEdit, read only EditText obect. So I assign the item and in getView, it stores the edit in a field. I call it like this:
private void processNewItem(C360ItemOwner itemOwner, int pos){
Item item = itemOwner.newItem(pos);
expAdapter.setFocusItem(item);//<--Assign it here
expAdapter.notifyDatasetChanged();
EditText edtFocus = expAdapter.getFocusEdit();
if (edtFocus != null)
edtFocus.requestFocus();
}
Then in getView of the adapter:
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
ViewGroup parent) {
if (convertView == null)
convertView = inf.inflate(R.layout.edit_group_item, null);
Item group = (Item) getGroup(groupPosition);
convertView.setTag(group);
EditText edtParent = (EditText) convertView.findViewById(R.id.edtParent);
edtParent.setTag(convertView);
scatterItemText(edtParent);
if (group == focusItem) {
Toast.makeText(edtParent.getContext(), "Found Focus Item " + groupPosition, Toast.LENGTH_SHORT).show();
focusEdit = edtParent;
}
return convertView;
}
I was very hopeful for this last method but this line:
EditText edtFocus = expAdapter.getFocusEdit();
Occurs BEFORE the getView() method runs which would assign it (so it's always null)! :-( plz halp
EDIT: I know this would be sloppy but what if I did ran a thread to wait a few hundred milliseconds so that getView() might run and then try to get focusEdit?

OK, figured it out with the help of Foamy Guy. My hunch about waiting a bit to check for the focusEdit worked out. I initially tried with 1000 milliseconds to give it plenty of time:
private void setFocus() {
EditText edtFocus = expAdapter.getFocusEdit();
if (edtFocus != null)
edtFocus.requestFocus();
else
Toast.makeText(getActivity(), "BLAH!", Toast.LENGTH_SHORT).show();
}
final Runnable r = new Runnable()
{
public void run()
{
setFocus();
}
};
private void processNewItem(C360ItemOwner itemOwner, int pos){
Item item = itemOwner.newItem(pos);
if (itemOwner.getItemCount() == 1)
checkHasItems();
expAdapter.setFocusItem(item);
resetListAdapter(true);
Handler handler = new Handler();
handler.postDelayed(r, 1000);//this works
//handler.postDelayed(r, 10);//this works too
//handler.postDelayed(r, 1);//this works too
//handler.post(r);//this too!
//r.run();//NOT this. This fails.
}
Then I tried 10 milliseconds and it still worked. Then just one millisecond and amazingly that worked too. Foamy suggested to try to just post without the delay and indeed that works. I finally tried to just run the runnable directly per Foamy's request but that failed.
So not sure, but this gets the job done. Thanks for the help guys.

Related

Android - Keeping clicked on items highlighted in ListView (and in memory)

I have an app whereby i am accessing a RESt webservice and presenting the request in listview (ListFrragment). It is essentially a list of articles. What i am trying to achieve is when a user clicks on an item in listview (expands article in ViewPager) is stays highlighted (indicating that the article has been read). The next time the user reloads this listview, all the item that have been read is still highlighted.
I have have created a boolean variable "read" for each article. When a user clicks on this item, the "read" variable is assigned to TRUE. Then in the custom Adapter(extends arrayAdpater), i assign the background colour (in this case green) in getView for articles whose "read" value is TRUE. This does work, but what i am finding is that other items in the listview is also randomly turning green when i start to scroll up and down the listview without actually clicking on it. I'm really stuck as i do not understand what is happening. Please can someone advise?
public void onListItemClick(ListView l, View v, int postion, long id) {
Article a = ((toReadListAdapter) getListAdapter()).getItem(postion);
// set read attribute to TRUE
a.setRead(true);
saveToReadList(toReadList);
// Open via ViewPager
Intent i = new Intent(getActivity(), ReadingList_PagerActivity.class);
startActivity(i);
Customer Adapter:
// Defining custom adapter
private class toReadListAdapter extends ArrayAdapter<Article> {
public toReadListAdapter(ArrayList<Article> listToRead) {
super(getActivity(), 0, listToRead);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getActivity().getLayoutInflater().inflate(R.layout.new_articlelistfragment, null);
}
Article en = getItem(position);
Log.d(TAG, "Showing articles: " + position + "/ pmid: " + en.getId());
Log.e(TAG, "isRead value: " + en.isRead());
if(en.isRead() == true){
convertView.setBackgroundColor(Color.parseColor("#C8E6C9"));
}
......
return convertView;
Inside the getView
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getActivity().getLayoutInflater().inflate(R.layout.new_articlelistfragment, null);
}
Article en = getItem(position);
if(en.isRead() == true){
convertView.setBackgroundColor(Color.parseColor("#C8E6C9"));
}
else{
// put the default color
convertView.setBackgroundColor( );
}
......
return convertView;
}
You are not applying the default color. Add an else statement in get view method

Listview with button onclick's position changes while scrolling

I have a listview and it's scrolable. I have few listview items and it's pulled from database. My problem is, each listview items have a button and i've setOnclickListener to the button inside getView. Now let's say there's 5 items and i tap on button for item number 1, the position is 0 and when i scroll to the end of the list, i can see the button for item number 5 is clicked. When i scroll till middle of the list, sometimes the button randomly clicked and i can see from my logcat, the particullar button's position is 0. How come?
Here is my getView' code
public View getView(final int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.newsfeed_layout, parent, false);
}
final Button btnLike = (Button) v.findViewById(R.id.buttonLike);
btnLike.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String buttonText = btnLike.getText().toString();
if(buttonText.equals("LIKE")){
Toast.makeText(postedItems.this, "Liked This item" + position, Toast.LENGTH_SHORT).show();
btnLike.setPadding(4,0,12,0);
btnLike.setText("UNLIKE");
btnLike.setTextColor(Color.parseColor("#ffffff"));
btnLike.setBackgroundResource(R.drawable.likedredbutton);
}else if(buttonText.equals("UNLIKE")){
Toast.makeText(postedItems.this, "Unlike This Item" + position, Toast.LENGTH_SHORT).show();
btnLike.setPadding(4,0,20,0);
btnLike.setText("LIKE");
btnLike.setTextColor(Color.parseColor("#737373"));
btnLike.setBackgroundResource(R.drawable.cornerstyledbutton);
}
}
});
return v;
}
First, to understand the problem: this occurs because ListView reuses views as you scroll, to improve performance. That's the explanation for the convertView parameter.
Because of this, you need to make sure that whatever state you want to store for each item is stored in the adapter itself or wherever you store its backing data -- and that when you implement getView(), the UI is fully updated to reflect this data (since it will have whatever properties you set on it the last time it was used).
In this case, you need to store whether each item is "liked" or "unliked". And then, always set the properties of btnLike to reflect this before returning.
As an example, your code would have to be more or less like this:
public View getView(final int position, View convertView, ViewGroup parent)
{
View v = convertView;
if (v == null) {
LayoutInflater vi = ...;
}
final Button btnLike = (Button) v.findViewById(R.id.buttonLike);
if (isLiked(position))
setButtonAsUnlike(btnLike);
else
setButtonAsLike(btnLike);
btnLike.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if (!isLiked(position))
{
// Not liked before, so like now.
setLiked(position, true); // store this value.
setButtonAsUnlike(btnLike); // the button is now "unlike"
}
else
{
// Liked before, unliked now.
setLiked(position, false); // store this value.
setButtonAsLike(btnLike); // the button is now "like"
}
}
});
return v;
}
where isLiked(position) queries the data, setLiked(position, boolean) updates it, and setButtonAsLike(Button) and setButtonAsUnlike(Button) update the visual properties of the Button as you are doing now.

ListView maintain state using Tag?

Apparently my thinking has some flaw as it's not working correctly. Basically, I'm trying to solve this problem: listview with checkbox
In the answer, people suggested to create a global data structure to hold the state, which made sense. However, I thought if I'm using ViewHolder pattern, I could use the tag as the structure to store state information?
cbox.setOnClickListener(new OnClickListener() {
public void onClick(View v)
{
P tag = (P) ((View) v.getParent()).getTag();
if(tag.cbox.isChecked())
tag.cbox.setChecked(true);
else
tag.cbox.setChecked(false);
//tag.cbox.toggle();
Log.d("YoYo", Boolean.toString(tag.cbox.isChecked()));
}
});
The code above did not toggle my checkbox in the rows. What did I do wrong?
Update: Rather then toggle, manual if statement seemed to work. Though, I'm running into another problem, where the checked state mess up after I scroll to different places. Why is that? If I set the checked state in the tag.cbox, isn't the check state unique to that object only?
Update2: I follow other's suggestion and got a working version, but I'm still wondering why setTag/getTag not working?
Working:
public View getView(final int position, View view, ViewGroup parent)
{
Plurker tag = getItem(position);
if (view == null)
view = adapterLayoutInflater.inflate(R.layout.adapter, null);
tag.avatar = (ImageView) view.findViewById(R.id.imgAvatar);
tag.cbox = (CheckBox) view.findViewById(R.id.cBox);
tag.cbox.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
public void onCheckedChanged(CompoundButton btn, boolean isChecked)
{
int pos = position;
Plurker p = getItem(pos);
p.isChecked = isChecked;
Log.d("PLURK", "Listener:" + p.toString());
}
});
tag.update(getItem(position));
Log.d("PLURK", tag.cbox.getText() + ":" + Integer.toString(position));
return view;
}
Not working:
public View getView(final int position, View view, ViewGroup parent)
{
Plurker tag = getItem(position);
if (view == null)
{
view = adapterLayoutInflater.inflate(R.layout.adapter, null);
tag.avatar = (ImageView) view.findViewById(R.id.imgAvatar);
tag.cbox = (CheckBox) view.findViewById(R.id.cBox);
tag.cbox.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0)
{
int pos = position;
Plurker p = getItem(pos);
p.isChecked = !p.isChecked;
Log.d("PLURK", "Listener:" + p.toString());
}
});
view.setTag(tag);
}
else
tag = (Plurker) view.getTag();
tag.update(getItem(position));
Log.d("PLURK", tag.cbox.getText() + ":" + Integer.toString(position));
return view;
}
In the "not working" version, I need to use onClick instead of onCheckedChanged event, because when the view from hidden to reappear, it called the event listener, so it would falsely triggered.
Are you inflating a new view each time or making use of the convertView that is passed in?
Normally the Adapter tries to recycle views, only creating enough to provide smooth scrolling. The existing recycled views are passed in as convertView. You can either inflate and return a new view every time (expensive) or just re-setup the convertView (if it exists) based on position. If recycling you need to re-set all the view attributes, as there is no guarantee that the recycled view you get is the same one used for this position in the past.
It sounds like your bug is that you are not correctly re-setting all the attributes of the recycled view (convertView) to match the data for the current position.

Listview in android did not refresh the view until dragged

I am using the code below:
private Runnable returnRes = new Runnable() {
#Override
public void run() {
if(m_orders != null && m_orders.size() > 0){
m_adapter.notifyDataSetChanged();
for(int i=0;i<m_orders.size();i++)
m_adapter.add(m_orders.get(i));
}
m_ProgressDialog.dismiss();
m_adapter.notifyDataSetChanged();
}
};
but the weird thing is, after the list populates, the only item available is the first thing on the list the rows directly below would be empty unless I drag down out of view then back again then it'd show. I'm pretty sure the code above is right as I followed a tutorial. But, I cant expect the user to drag down and back again to see the things involved...
And to add, I just noticed that my datas are not populated properly as this warning would appear 07-19 23:54:49.947: WARN/InputManagerService(58): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy#44eb97c0
and I'm quite sure that my codes are correct and the following is where it stops:
public View getView(int position, View convertView, ViewGroup parent){
View v = convertView;
if(v != null){
return v;
}
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
Log.d("added", "g" + position);
Grade g = grades.get(position);
if(g != null){
TextView name = (TextView) findViewById(R.id.bottomtext);
TextView id = (TextView) findViewById(R.id.toptext);
if(name != null)
name.setText(g.getName());
if(id != null)
id.setText(g.getId());
Log.d("grade", "grade " + g.toString());
}
return v;
}
and from the LogCat trace I would only get to position 3 :( what could be the problem?
someone please help me...
LoginByHttpPost gradeIndex = new LoginByHttpPost();
HttpURLConnection gradePage = gradeIndex.doHttpGet(TARGETURL);
String gradeInd = gradeIndex.readResponse(gradePage);
Document doc = Jsoup.parse(gradeInd);
// do more things here
Log.d("grade now ", grades.get(0).text());
Log.d("gradef now ", gradesF.text());
for(int i = 0; i < grades.size(); i += 5){
Grade grade = new Grade();
grade.setId(grades.get(i).text());
grade.setName(grades.get(i + 1).text());
//gradeList.add(grade);
ga.add(grade); //this is my arrayadapter not sure where to add my object to through :(
}
for(int i = 0; i < gradesF.size(); i++){
gradeList.get(i).setGrade(gradesF.get(i).text());
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
Log.d("prob", e.getMessage());
}
this is called from the asyncatask in the function doInBackground()
Try calling ListView.invalidateViews() on the list view. Worked for me.
Even if you call notifyDataSetChanged() and/or notifyDataSetInvalidated() from the UI thread on the adapter, these only invalidates the data and not the views. Hence.
You should call notifyDataSetChanged() in the UI thread try using runOnUiThread().
The second thing is notifyDataSetChanged() should be called only after add, remove and clear functions.
You could try refreshing the listview by calling listView1.requestLayout() or listView1.setAdapter(adapter). You could also try adapter.notifyDataSetChanged(). If scrolling on listview makes the views visible, you could also try scrolling the listview to the bottom and then scroll back to the original position programmatically.
UPDATE:
I think the problem may be coming from your getView() function. Try changing it to this:
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
if (v == null)
{
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
Log.d("added", "g" + position);
}
Grade g = grades.get(position);
if(g != null)
{
TextView name = (TextView) findViewById(R.id.bottomtext);
TextView id = (TextView) findViewById(R.id.toptext);
if(name != null)
{
name.setText(g.getName());
}
if(id != null)
{
id.setText(g.getId());
}
Log.d("grade", "grade " + g.toString());
}
return v;
}
Ok, I solved the problem.
There is nothing wrong with the ListAdapter. The problem is from the parent views of the ListView.
onMeasure must be called on the ListView every time the layout is changed. i.e. onMeasure or onLayout is called on one of its parents.
I had a custom view as the parent of the parent of the ListView. In which I precisely refused to measure the children to make the layout process faster.
You want to do something in background then send some change to UI, right? If you are doing this, you should use AsyncTask, a simpler and more effective way for background processing. Whenever your want to change the UI, just call onProgressUpdate() then do what you want there.
I had a similar problem. A simple file manager: if I have an image I've to resize it with a separate thread. So I show a placeholder until the resized image is ready. After that I've to call notifyDataSetChanged() on the adapter. My solution is to use an handler like this on the adapter
public final Handler fileArrayAdapterHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
notifyDataSetChanged();
}
};
On the thread I send an empty message to the handler at the end of it....
With different message you could do many other things ...
i was having the same issue, what i was missing was that the position was not always been sent, for example was skipping (position 0 and 2) and these were no updating until i scrolled.
This fix it for me (See that i used an asynctask) went from this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
...
new AsyncTask<ViewHolder, Void, Bitmap>() {
private ViewHolder v;
#Override
protected Bitmap doInBackground(ViewHolder... params) {
// Code
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (v.position == position) {
// Code
}
}
}.execute(viewHolder);
return convertView;
}
To this (Created an inner class, pass the position in the constructor):
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
....
new DownloadImage(position).execute(viewHolder);
return convertView;
}
private class DownloadImage extends AsyncTask<ViewHolder, Void, Bitmap> {
private ViewHolder v;
private int myPosition;
public DownloadImage(int p) {
myPosition = p;
}
#Override
protected Bitmap doInBackground(ViewHolder... params) {
// Code
return result;
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (v.position == myPosition) {
// Code
}
}
}
As some others have already stated, this problem is caused by the fact that the code is not called on the UI thread since you are executing it from an AsyncTask. Since I cannot comment yet, here's another answer.
I was facing the same issue: I updated the data (which was held in a static context), but the listview did not update after calling notifyDataSetChanged() on the adapter. After dragging/scrolling, the UI is updated and the data automatically refreshed.
The issue is that only the original thread that created a view hierarchy can touch its views. I suppose you are running the Runnable from the callback Thus, you need to call it on the UI thread for the listview to update itself, in a fragment you would do something along the lines of:
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
// Change the data source here,
// eg. some ArrayList<ItemObject> in this case
someDataSource.clear();
someDataSource.add(new ItemObject());
// ...
// And notify the adapter of the changes to the data source
adapter.notifyDataSetChanged();
}
});
If you run adapter.notifyDataSetChanged() outside the UI thread, you will usually also run into a CalledFromWrongThreadException, some try catch block might have masked that.
Same as #ac19 's answer, problem was sloved by adding handler-message.
I use custom adapter and typical ListView, will update data if I get bluetooth callback. When I called "Adapter.notifyDataSetChanged()" in callback function, List didn't updated until I touched screen.
I defiend a message and add following code in callback function (replaced Adapter.notifyDataSetChanged())
Message m = new Message();
m.what = MessageYouDefined;
mHandler.sendMessage(m);
And added handler in onCreate
mHandler=new Handler(){
public void handleMessage(Message msg)
{
switch (msg.what){
case UpdateChargerList:
chargerAdapter.notifyDataSetChanged();
break;
}
super.handleMessage(msg);
}
};

Android ListView: getTag() returns null

Hallo all,
I have a ListView which contains a Button in each line. The following code is part of the getView() Method
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
TextView tv;
Button saveA_button;
EditText edittext;
FITB_ViewWrapper wrapper;
if (row == null) {
LayoutInflater li = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (ChooseMode_Act.modeInfo.equalsIgnoreCase("Training")) {
row = li.inflate(R.layout.exercise_for_training_fitb,parent, false);
}else {
row = li.inflate(R.layout.exercise_for_exam_fitb,parent, false);
}
wrapper=new FITB_ViewWrapper(row);
row.setTag(wrapper);
if (ChooseMode_Act.modeInfo.equalsIgnoreCase("Exam")) {
saveA_button=wrapper.getSaveAnswer_Button();
OnClickListener l=new OnClickListener() {
#Override
public void onClick(View v) {
Integer mp=(Integer)v.getTag();
Log.i("mp","my Position is: "+mp);
}
};
saveA_button.setOnClickListener(l);
}
}else {
wrapper=(FITB_ViewWrapper) row.getTag();
}
For my App i need to known to which item the Button belongs to, so i try to detect it. The code
Log.i("mp","my Position is: "+mp);
puts out a message: mp myPosition is: null
I can't understand, why do i get a "null" but not an Integer? How can i find out the Position of an Item in a ListView?
Thanks a lot.
Log.i("mp","my Position is: "+position);
you have the position already !
public View getView(final int position, View convertView, ViewGroup parent) {
The Views in a ListView are reused as you scroll up and down. Thus, setting values in getView often has unintented consequences, like the image that you meant to set for item number 5 appearing in item number 10, 15 and 20 also.
To avoid this, if you want to set properties in getView you need to make sure you set or unset them for each view.
I'm not sure what exactly you are trying to accomplish with your code, but it might help to move the setTag outside of the if statement, to make sure you are setting it each time that a view is used.

Categories

Resources