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
895 views
in Technique[技术] by (71.8m points)

listview - Android using custom object with ArrayAdapter, and communicating back views

There are some similar questions to this but I can't find one that goes as far as I want. I am using a custom class stored in an ArrayAdapter to populate a ListView with linear layouts that I can customise by modifying the class - which is working in that I have a button in each which does exactly what I want. The LinearLayout looks like: [TextView][EditText][Button] and when the Button is clicked, it does some DB stuff with the contents of the EditText. That's all fine.

The problem I have is that I want to (for example) update the text contents of an EditText in 1 layout within the ListView, in my activity rather than in the class where the view is created and the Button listener set and so on. I may also want to say "disable the Button in the second item of the list view" etc..

I've tried a lot of different approaches, passing around contexts or calling methods on the adapter vs on the actual list of objects, making the EditText a member variable with a getter, and I always get either nothing happening or I reach a NullPointerException when looking for the EditText by ID.

Is this even possible? Do I need to create a final variable to hold each EditText and somehow pass this back to the activity or something? I will end up with multiple views within my activity that have the same id, but they'll have been created by separate objects

Ultimately what I'm trying to do is make it so I can abstract my GUIs a bit. Currently I have 8 activities which are about 90% identical, but I have duplicated code setting up the views in each activity. I want to instead abstract this out into classes where I can say "give me a new text input line with whatever label and I will override the button behaviour" and then refactor down to 1 activity for this task. When a user inputs data in one of the 'rows' then the activity may need to update one of the other rows, (or clear 1 when the back button is pressed etc) which is why I want to be able to call back to a specific view within the layout within the object within the array adapter :D

for example I want to be able to create 3 new textInputLineBuilder objects (probably subclass this eventually) and have them displayed like:

[Name:][NameEditText][Button]

[Stock:][StockEditText][Button]

[Quantity:][QuantityEditText][Button]

here's the (cut down) layout, activity_row.xml which the custom object inflates and returns to the ArrayAdapter

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
    <TextView
        android:id="@+id/rowLabel"/>    
    <EditText
        android:id="@+id/rowInput"/>    
    <Button
        android:id="@+id/rowButton"/>
</LinearLayout>

here is the relevant code in the activity:

public class AnActivity implements UICallBack {
    private ListView listOfInputs;
    private ArrayList<UIElementInterface> listOfUIElements;
    private UIElementsAdapter UIAdapter;
    
    protected void onCreate() {
        listOfInputs = (ListView) findViewById(R.id.listInputs);
        
        listOfUIElements = new ArrayList<>();
        listOfUIElements.add(new textInputLineBuilder("<label>", this));    //this implements a callback interface
        //add a few more

        UIAdapter = new UIElementsAdapter(this.getApplicationContext(), listOfUIElements);
        UIAdapter.notifyDataSetChanged();
        listOfInputs.setAdapter(UIAdapter);
    }
    
    public void updateUIState() {
        //I have tried all sorts here
        for (int i=0; i<listOfUIElements.size(); i++) {
            UIAdapter.setTheText("test "+i, i);
        }

        UIAdapter.notifyDataSetChanged;
        listOfInputs.setAdapter(UIAdapter);
    }
}

I then have my ArrayAdapter class, UIElementsAdapter.java which retrieves the appropriate object from the list and then calls getRowView in order to get the object to inflate the layout that will make up this 'row'

public class UIElementsAdapter extends ArrayAdapter<UIElementInterface> {
    private final Context context;

    public UIElementsAdapter(Context context, ArrayList<UIElementInterface> listOfUIElements) {
        super(context, 0, listOfUIElements);
        this.context = context;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return getItem(position).getRowView(context, parent);   //retrieves the UIElementInterface object aka textInputLineBuilder object
    }

    public void setTheText(String s, Integer position) {
        try {
            getItem(position).setEditText(s);
        } catch(NullPointerException e) {
            e.printStackTrace();
        }
    }
}

and finally I have textInputLineBuilder.java which implements the UIElementInterface (getRowView & setEditText)

public class textInputLineBuilder implements UIElementInterface {
    private View convertView;

    public textInputLineBuilder(String label, UICallBack callback) {
        this.labelText = label;
        this.callback = callback;   //used with button onclicklistener
    }
    
    public View getRowView(Context context, ViewGroup parent) {     
        LayoutInflater layout = LayoutInflater.from(context);
        convertView = layout.inflate(R.layout.activity_row, parent, false);

        label = (TextView) convertView.findViewById(R.id.rowLabel);
        fuzzy = (Button) convertView.findViewById(R.id.rowButton);
        input = (EditText) convertView.findViewById(R.id.rowInput);
        final EditText inputFinal = input;    //for the button listener.  was trying using input as a member variable for setEditText but that didn't work
        
        label.setText(labelText);
        
        //add onclicklistener for the fuzzy button. works fine
        
        return convertView;
    }
    
    public void setEditText(String s) {
        //I have tried all sorts here, passing in context from various places, passing in layout inflaters etc
        EditText e = (EditText) convertView.findViewById(R.id.rowInput);
        e.setText(s);
    }
}

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

1 Reply

0 votes
by (71.8m points)

In updateUIState(), you currently try to modify the EditTexts directly by calling setTheText(). Using the debugger I was able to see that this works to a certain extent - the EditText has the desired value at the end of setEditText(). But since the EditText is managed by an adapter, setting the text alone does not have the desired effect. If one calls e.invalidate() afterwards, the text is displayed correctly, if one also skips adapter.notifyDatasetChanged().

So it's basically possible to modify the Views. But it's a bad idea to do it that way.

Instead, you should change the data class. In your case, you need a way to set the desired text (String) for the EditText in TextInputLineBuilder. Then when getRowView() is called, this updated text should be used with EditText input.

Back to updateUIState(): it's correct to call notifyDatasetChanged() after modifying the data list, but you do not need to re-assign the adapter to the ListView.


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

...