androidlistviewandroid-spinnerandroid-sensors

Updating TextView without losing Spinner focus in busy ListView


I sample sensors (That's make the ListView busy) with custom user rate and I show the values in my listview, therefore I have a custom listview with 3 textview (for x, y and z values) and one spinner.

My layout is:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="fill_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:minWidth="140dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="X:"
            android:id="@+id/tvX"
            android:focusable="false"
            android:focusableInTouchMode="false"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Y:"
            android:focusable="false"
            android:focusableInTouchMode="false"
            android:id="@+id/tvY" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Z:"
            android:focusable="false"
            android:focusableInTouchMode="false"
            android:id="@+id/tvZ" />
    </LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal"
        android:layout_marginLeft="30dp">

        <Spinner
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:id="@+id/spinner"/>
    </LinearLayout>

</LinearLayout>

and I using this code as adapter:

public class customListView extends BaseAdapter
{
    public Activity context;
    ArrayList<MyActivity.SensorProperties> sensorPropertieses;
    public String[] spinnerValues;

    public LayoutInflater inflater;


    public  customListView(Activity context, ArrayList<MyActivity.SensorProperties> sensorPropertieses, String[] spinnerArray)
    {
        super();
        this.context = context;
        this.sensorPropertieses = sensorPropertieses;
        spinnerValues = spinnerArray;
        this.inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getCount() {
        return sensorPropertieses.size();
    }

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

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

    class ViewHolder
    {
        TextView Xvalue, Yvalue, Zvalue;
        Spinner spinner;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup)
    {
        ViewHolder holder;
        if (view == null)
        {
            holder = new ViewHolder();
            view = inflater.inflate(R.layout.custom_layout, null);

            holder.Xvalue = (TextView) view.findViewById(R.id.tvX);
            holder.Yvalue = (TextView) view.findViewById(R.id.tvY);
            holder.Zvalue = (TextView) view.findViewById(R.id.tvZ);
            holder.spinner = (Spinner) view.findViewById(R.id.spinner);

            // populate spinner
            ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>
                    (view.getContext(), android.R.layout.simple_spinner_item, spinnerValues);
            holder.spinner.setAdapter(dataAdapter);
            
            view.setTag(holder);
        }
        else
            holder = (ViewHolder) view.getTag();


        // update sensor values
        holder.Xvalue.setText(String.valueOf("X: " + sensorPropertieses.get(i).x));
        holder.Yvalue.setText(String.valueOf("Y: " + sensorPropertieses.get(i).y));
        holder.Zvalue.setText(String.valueOf("Z: " + sensorPropertieses.get(i).z));

        return view;
    }
}

my main activity code is (the layout contain only a button and listview):

public class MyActivity extends Activity implements SensorEventListener
{
    public class SensorProperties
    {
        public float x = 0, y = 0, z = 0;
    }

    ListView listView;
    ArrayList<SensorProperties> sensorPropertieses = new ArrayList<SensorProperties>();
    customListView adapter;
    SensorManager sensorManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        // let's work only on two nodes
        sensorPropertieses.add(new SensorProperties());
        sensorPropertieses.add(new SensorProperties());

        listView = (ListView) findViewById(R.id.listView);
        String[] spinnerValues = new String[] {"A", "B", "C"};
        adapter = new customListView(MyActivity.this, sensorPropertieses, spinnerValues);
        listView.setAdapter(adapter);

        Button btnRegister = (Button) findViewById(R.id.buRegister);
        btnRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view)
            {
                RegisterToSensors();
            }
        });
    }

    void RegisterToSensors()
    {
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_FASTEST);
        sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_GAME);
    }

    @Override
    public void onSensorChanged(SensorEvent sensorEvent)
    {
        SensorProperties tmp = new SensorProperties();
        tmp.x = sensorEvent.values[0];
        tmp.y = sensorEvent.values[1];
        tmp.z = sensorEvent.values[2];
        if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
            sensorPropertieses.set(0, tmp);
        else
            sensorPropertieses.set(1, tmp);
        adapter.notifyDataSetChanged();
    }
    ///...
}

The question: How should I work with ListView contains TextView and Spinner without lost spinner focus although I update the TextView frequency?

Edit

I change question title and body thanks to mmlooloo comments.

To be clear:

Edit 2

Currently, I'm not sure if the issue is the losing spinner focus or not. for debugging I add sleep of 30ms before I updating sensors value in the textviews (before // update sensor values) in my custom list view:

try {
    Thread.sleep(30);
} catch (InterruptedException e) {
    e.printStackTrace();
}

And I success to drop-down the spinner and choose another item while the textview updating (everything is very slow, but works!).

Edit 3

I have find, right now, probably the issue is with adapter.notifyDataSetChanged() because I cannot drop down the spinner also when I gray-out the textview prints.

Bottom line, I'm not really know what is the root cause but I cannot drop-down the spinner when I update text views frequency in listview, and the question is why?


Solution

  • Issue here is you are frequently calling adapter.notifyDataSetChanged(); in your onSensorChanged method, so it binds listview every time so does your getView is called every time and which sets spinner value every time.

    What I did is, accessed your listview first and second child in onSensorChanged method and from that I have accessed texviews and update them as frequently as you want and you can access your spinner too.

    @Override
    public void onSensorChanged(SensorEvent sensorEvent) {
        // SensorProperties tmp = new SensorProperties();
        // tmp.x = sensorEvent.values[0];
        // tmp.y = sensorEvent.values[1];
        // tmp.z = sensorEvent.values[2];
        // if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
        // sensorPropertieses.set(0, tmp);
        // else
        // sensorPropertieses.set(1, tmp);
        // adapter.notifyDataSetChanged();
    
        Log.i("tag", "ensorEvent.values[0] = " + sensorEvent.values[0]);
    
        if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            View childView = (View) listView.getChildAt(0);
            TextView xTextView = (TextView) childView.findViewById(R.id.tvX);
            xTextView.setText(String.valueOf(sensorEvent.values[0]));
            TextView yTextView = (TextView) childView.findViewById(R.id.tvY);
            yTextView.setText(String.valueOf(sensorEvent.values[1]));
            TextView zTextView = (TextView) childView.findViewById(R.id.tvZ);
            zTextView.setText(String.valueOf(sensorEvent.values[2]));
        } else {
            View childView = (View) listView.getChildAt(1);
            TextView xTextView = (TextView) childView.findViewById(R.id.tvX);
            xTextView.setText(String.valueOf(sensorEvent.values[0]));
            TextView yTextView = (TextView) childView.findViewById(R.id.tvY);
            yTextView.setText(String.valueOf(sensorEvent.values[1]));
            TextView zTextView = (TextView) childView.findViewById(R.id.tvZ);
            zTextView.setText(String.valueOf(sensorEvent.values[2]));
        }
    
    }