3

I have a ListView where each item contains multiple EditTexts. I'm defining a scale, so one of the EditTexts is dependent on an EditText in the item above. In the example below, B is set to the same value as A when A has been edited. Thus, the user doesn't actually use the right column.

I'm able to listen to events in A and update B and call notifyDataSetChanged() accordingly, but then I lose focus of my EditText as the ListView is redrawn.

enter image description here

Bottom line, what I want is:

Edit field A and when I then tap on field C, I'd like A's value to apply to B and I'd like focus to go to C.

It's the "focus goes to C" part that I'm having trouble with.

p.s. There's actually another EditText on the row, so focus won't always be going to the column that has A and C.

Edit: Here is my adapter's getItemId() method, to help answer @aniv's comment.

@Override
    public Object getItem(int arg0) {
        return arg0;
    }

EDIT_2: After overriding hasStableIds() to return true, it works great when I go from A to a third column in another item, but not when I go from A to C. I think my onFocusChange override is messing something up because it only behaves strangely when changing focus in that column (in which share the same onFocusChange method). Here's my custom onFocusChangeListener.

private class MyFocusChangeListener implements View.OnFocusChangeListener{
    private EditText et;
      private ScaleItem item;
      private Integer pos;

      private MyFocusChangeListener(ScaleItem item, Integer pos){
          this.item = item;
          this.pos = pos;
      }

      @Override
      public void onFocusChange(View v, boolean hasFocus){
          if(!hasFocus){
              et = (EditText) v;
              Activity_EditCourseGPA a = (Activity_EditCourseGPA) activity;
              a.updateMaxOnTextChange(item, pos, et.getText().toString());
          } else {

          }
      }
}

The function it refers to in my activity (to use the EditText field to update underlying data)....

public void updateMaxOnTextChange(ScaleItem item, Integer pos, String str){
    if(pos < scaleItems.size()){
        scaleItems.get(pos + 1).setMax(Double.valueOf(str));
        scaleListAdapter.notifyDataSetChanged();
    }
}

I use the EditText position and contents to update the underlying data at the same position. Here's a glimpse of what happens when I change focus within the same column. Very weird.

enter image description here

NSouth
  • 5,067
  • 7
  • 48
  • 83
  • Does your adapter have stable IDs? – alanv Oct 19 '14 at 23:41
  • @alanv What exactly do you mean by stable IDs? My adapter inflates a layout xml, which contains views which are uniquely identified. – NSouth Oct 19 '14 at 23:42
  • Does your adapter return true from hasStableIds() and return consistent values from getItemId(int)? – alanv Oct 19 '14 at 23:43
  • @alanv `hasStableIds()` returns false. I think I get consistent values for `getItemId()`--I've posted my code for it above. What does this mean and how does it affect what I'm trying to do? – NSouth Oct 19 '14 at 23:52
  • Stable IDs allow ListView to reuse the same View for a given item when performing layout after a data set change. It also allows it to attempt to preserve focus. Try overriding hasStableIds() to return true. – alanv Oct 20 '14 at 00:42
  • @alanv That seems to have gotten me closer. Please see my 2nd edit above for the difficulties above. Thanks so much for helping, by the way. – NSouth Oct 20 '14 at 01:06
  • have you succeeded to get that done ? – MSaudi Oct 29 '14 at 17:08
  • @MSaudi, I ended up abandoning the ListView and using an array of LinearLayouts because I always know the exact number of rows. Because this view is static and not dynamic, focus behaves much better. – NSouth Oct 29 '14 at 17:14
  • @NSouth thanks for your replay. It's not doable in my case, i'm still trying to figure it out. weird problem – MSaudi Oct 29 '14 at 17:17
  • @MSaudi, the link below helped clarify some of it for me. The issue is that the views in the list get recycled, so while the data on a row that just scrolled into view my be new, that data is sitting on the exact same view that just scrolled *out of* view. So a single view is not tied to a single data item, which makes things quite complicated. It also means the ListView readjusts when scrolled, or the soft keyboard shows up, or anything happens to change what is visible in the list. I'm sure it can be figured out, but probably takes lots of work. http://stackoverflow.com/a/26521356/3165621 – NSouth Oct 29 '14 at 17:25

1 Answers1

0

Firstly, you need a custom class Grade

public class GradeInfo {
  boolean enabled;
  String gpa, perc1, perc2;
}

You need to create a List<GradeInfo> object which will contain list of GradeInfo objects, each representing a row. You can use this List in your listview's adapter. Your adapter will look like this:

private class GradeAdap extends BaseAdapter { 

    @Override
    public int getCount() {
        return gradeList.getSize();
    }

    @Override
    public Object getItem(int position) {
        return gradeList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {

        if(convertView == null)
            convertView = getLayoutInflater().inflate(R.layout.row_grade, null);

        return loadGradeRow(convertView, position);
    }
}

Yes, the getView method of the adapter calls another method loadGradeRow:

private View loadGradeRow(final View rowView, final int position) { 
  CheckBox chk= (CheckBox ) rowView.findViewById(R.id.chk);
  ...// Declare and initialize other views

  listIsRefreshing = true; // Explained below
  chk.setChecked(gradeList.get(position).enabled);
  ...// set views accordingly 
  listIsRefreshing = false;

  txtGpa.addTextChangedListener(new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {                   
        }
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count,
                int after) {                    
        }
        @Override
        public void afterTextChanged(Editable s) {

            if(listIsRefreshing) //JUST REFRESHING, DON'T MIND
                return;

            gradeList.get(position).perc1 = s.toString();

            if(position == gradeList.size()-1) //Dont assign if its the last row
                return;


            gradeList.get(position+1).perc2 = s.toString();

            loadGradeRow(listview.getChildAt(position+1), position+1);
        }
    });

}

listIsRefreshing is a boolean which needs to be declared in your activity. Its needed to make sure value is assigned to perc2 only when user has entered the value manually.

desidigitalnomad
  • 1,443
  • 21
  • 33