1

I'm trying to create a ListView (with custom CursorAdapter) of EditText items such that EditTexts appear uneditable at first and become editable upon long click. Then the user would edit EditText contents and EditText would save changes to db upon losing focus. However I've run into a really nasty behaviour that prevents me from doing this.

1) I've set my EditTexts to android:focusable="false" and android:focusableInTouchMode="false" in XML.

2) I've created an OnItemLongClickListener in my ListActivity that does the following:

public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
    Log.d("NLAc", "longClick called");
    EditText et = (EditText) view.findViewById(R.id.etFolderName);
    et.setFocusable(true);
    et.setFocusableInTouchMode(true);
    return true;
}

3) And when I create views in my adapter I attach the following focus change listener:

public void onFocusChange(View v, boolean hasFocus) 
{
    if (hasFocus)
{
    EditText et = (EditText)v;
    Log.d(TAG, "hasFocus true called " + et.getText());
    et.setText("focused");
    et.setSelection(et.length());
}
else
{
    EditText et = (EditText)v;
    Log.d(TAG, "hasFocus false called " + et.getText());
    et.setText("unfocused");
    et.setFocusableInTouchMode(false);
            //TODO Save to DB
}

}

What happens is that when I long click the very first item I get the following in the log:

longClick called

hasFocus true called item1

hasFocus false called focused

If I remove the line setting focusable to false (et.setFocusableInTouchMode(false);) I get another hasFocus true called unfocused. Apparently things go like this:

1) EditText gets focus when set focusable

2) LinearLayour containing my ListView loses focus and calls internal unFocus() on all it's children including my EditText

3) EditText loses focus

4) EditText gets focus - for whatever reason now.

This behaviour prevents me from disabling EditText upon losing focus or making it unfocusable until the next long click comes through which is what I want. Can anyone suggest what I may be missing? Or explain this behaviour? Any solutions appreciated.

devmiles.com
  • 9,895
  • 5
  • 31
  • 47
  • are you sure it's a good thing to save the users input this way? you usually have a button to save user's input data and go further along with your data flow. or save it when activity goes onPause() or user leaves an activity. it will save you a bunch of time and avoid pain as well. just saying... – Sergey Benner Jan 23 '12 at 19:54
  • I'm thinking about adding a little 'done' button but I was really hoping that I could just save on anything, like losing focus or 'enter' on soft keyboard. – devmiles.com Jan 23 '12 at 20:06
  • 1
    I understand but I would really recommend looking at http://ux.stackexchange.com/questions/8868/best-practices-for-uidesign-on-android-usability-ux and of course at http://www.androidpatterns.com and here goes your ENTER soft KB button http://stackoverflow.com/questions/4451374/use-enter-key-on-softkeyboard-instead-of-clicking-button – Sergey Benner Jan 23 '12 at 20:11
  • Thanks for interesting links, but I'm still eager to understand what's going on with focus :) – devmiles.com Jan 23 '12 at 21:55

2 Answers2

2

I was able to make some progress by using two views on top of each other. I'm using a TextView for unfocused display, and I hide it and show an EditText instead on long clicked. But that didn't really help at first with ListView's really weird focus handling until I've experimented with android:descendantFocusability setting defined for ViewGroups. After I've set it to ViewGroup.FOCUS_AFTER_DESCENDANTS it's behaviour became much more predictable, at least for me.

devmiles.com
  • 9,895
  • 5
  • 31
  • 47
1

OK. Here is your code. hope it suits your needs or at least that's what I've gotten from your post :)


import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.text.InputFilter;
import android.text.Spanned;
import android.util.Log;
import android.view.*;
import android.widget.*;
import java.util.ArrayList;
import java.util.List;

public class EditListActivity extends Activity {


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout ll = new LinearLayout(this);
        LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
        ll.setLayoutParams(lp1);
        ll.setOrientation(1);
        ListView lv = new ListView(this);
        MyListAdapter adapter = new MyListAdapter(this, null);
        lv.setAdapter(adapter);
        ll.addView(lv);
        setContentView(ll);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }


    class MyListAdapter extends BaseAdapter implements View.OnLongClickListener, View.OnFocusChangeListener {
        private final String TAG = ListActivity.class.getName();
        List<String> stringsarray;
       private Context context;

        public MyListAdapter(Context context, List<String> phonebook) {
            this.stringsarray = new ArrayList<String>();

            this.stringsarray.add("test1");
            this.stringsarray.add("test2");
            this.stringsarray.add("test3");
            Log.d(TAG, "created list adapter");
            this.setContext(context);
        }

        public int getCount() {
            return this.stringsarray.size();
        }

        public String getItem(int position) {
            return this.stringsarray.get(position);
        }

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


        public View getView(final int position, View convertView, ViewGroup parent) {
            LinearLayout rl = new LinearLayout(getContext());

            rl.setOrientation(1);
            EditText text = new EditText(getContext());

            text.setOnLongClickListener(this);
            text.setOnFocusChangeListener(this);
            text.setText(this.stringsarray.get(position));

            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.FILL_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT);

            text.setFocusable(true);
            rl.addView(text, lp);
            return rl;
        }

        public void onFocusChange(View view, boolean b) {
            EditText et = (EditText) view;
                et.setFilters(new InputFilter[]{
                        new InputFilter() {
                            public CharSequence filter(CharSequence src, int start,
                                                       int end, Spanned dst, int dstart, int dend) {
                                return src.length() < 1 ? dst.subSequence(dstart, dend) : "";
                            }
                        }
                });
            if (b) {
                Log.d(TAG, "hasFocus true called " + et.getText());
                et.setText("focused");
                et.setSelection(et.length());

            } else {

                Log.d(TAG, "hasFocus false called " + et.getText());
                et.setText("unfocused");
                //TODO Save to DB
            }
        }

        public boolean onLongClick(View view) {
            getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
            final EditText et = (EditText) view;
            et.setFilters(new InputFilter[]{
                        new InputFilter() {
                            public CharSequence filter(CharSequence src, int start,
                                                       int end, Spanned dst, int dstart, int dend) {
                               return src;
                            }
                        }
                });
            et.setText("editable");
            Log.d(TAG, "on long click called " + et.getText());
            et.requestFocus();
            et.setFocusableInTouchMode(true);

            return true;
        }

        public Context getContext() {
            return context;
        }

        public void setContext(Context context) {
            this.context = context;
        }
    }

}

have fun.

Sergey Benner
  • 4,421
  • 2
  • 22
  • 29
  • Sorry you had to go that far, but it's not what I'm looking for. It's just a list of editable edittexts that can be rotated using simple clicks. I want a listview of edittext that become editable only after you long clicked on one of them. – devmiles.com Jan 24 '12 at 08:06
  • that's exactly what it does. have you tried to compile and test it? – Sergey Benner Jan 24 '12 at 09:44
  • I have updated the code to make up the correct disabling of the editing and showing soft keyboard. It should be fine now. Tested on my phone with 2.2.2 + emulator on 2.3.3 – Sergey Benner Jan 24 '12 at 10:50
  • Well, now your EditTexts are focusable and enabled but can't be edited, not really what I was looking for :) – devmiles.com Jan 24 '12 at 11:29
  • If you want them setEnabled(false) it wont work this way - they will be disabled for everything for any clicks or whatsoever. If they are not focusable you will not be able to tap on them again and do your onLongClicks onClicks again. You can play with background images when they're not editable to make a hint for a user. Otherwise you should rethink your design perhaps. – Sergey Benner Jan 24 '12 at 11:44