Autore Topic: ListView con rows custom  (Letto 3100 volte)

Offline fape

  • Nuovo arrivato
  • *
  • Post: 3
  • Respect: 0
    • Mostra profilo
  • Dispositivo Android:
    htc hd - galaxy tab - xoom
ListView con rows custom
« il: 10 Agosto 2011, 13:55:54 CEST »
0
Salve a tutti,
recentemente mi sono imbattuto in un problema che, stano, risulta essere molto poco documentato:
avere una ListView con rows customizzate e possibilità di avere single o multiple choices.
Per ottenere questo è sufficiente creare dei LinearLayout che implementano l'interfaccia Checkable.
Vi prego, se l'argomento è già stato trattato... non infamatemi!
Posto qui di seguito un piccolo esempio di come poterlo  ottenere.
1) innanzitutto creiamo la nostra row della lista:

Codice (XML): [Seleziona]
<?xml version="1.0" encoding="utf-8"?>
<test.list.CheckableLinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="wrap_content">
    <TextView android:id="@+id/textView1"
             android:text="Uno"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:layout_width="wrap_content"
                          android:layout_height="match_parent">
    </TextView>
    <TextView android:id="@+id/textView2"
                          android:text="Due"
                          android:textAppearance="?android:attr/textAppearanceSmall"
             android:layout_width="wrap_content"
                          android:layout_height="match_parent">
    </TextView>
    <TextView android:id="@+id/textView3"
                          android:text="Tre"
                          android:textAppearance="?android:attr/textAppearanceSmall"
             android:layout_width="wrap_content"
                          android:layout_height="match_parent">
    </TextView>
   
</test.list.CheckableLinearLayout>

Codice (Java): [Seleziona]
public class CheckableLinearLayout extends LinearLayout implements Checkable {
       
        private boolean mChecked;
    private int mCheckMarkResource;
    private Drawable mCheckMarkDrawable;
    private int mBasePaddingRight;
    private int mCheckMarkWidth;

    private static final int[] CHECKED_STATE_SET = {
        android.R.attr.state_checked
    };

        public CheckableLinearLayout(Context context) {
                super(context);
        }

        public CheckableLinearLayout(Context context, AttributeSet attrs) {
                super(context, attrs);
                setBackgroundColor(0xFF000000);
        }

        public void toggle() {
        setChecked(!mChecked);
    }
   
    public boolean isChecked() {
        return mChecked;
    }

    /**
     * <p>Changes the checked state of this text view.</p>
     *
     * @param checked true to check the text, false to uncheck it
     */

    public void setChecked(boolean checked) {
        if (mChecked != checked) {
            mChecked = checked;
            refreshDrawableState();
        }
    }


    /**
     * Set the checkmark to a given Drawable, identified by its resourece id. This will be drawn
     * when {@link #isChecked()} is true.
     *
     * @param resid The Drawable to use for the checkmark.
     */

    public void setCheckMarkDrawable(int resid) {
        if (resid != 0 && resid == mCheckMarkResource) {
            return;
        }

        mCheckMarkResource = resid;

        Drawable d = null;
        if (mCheckMarkResource != 0) {
            d = getResources().getDrawable(mCheckMarkResource);
        }
        setCheckMarkDrawable(d);
    }

    /**
     * Set the checkmark to a given Drawable. This will be drawn when {@link #isChecked()} is true.
     *
     * @param d The Drawable to use for the checkmark.
     */

    public void setCheckMarkDrawable(Drawable d) {
        if (d != null) {
            if (mCheckMarkDrawable != null) {
                mCheckMarkDrawable.setCallback(null);
                unscheduleDrawable(mCheckMarkDrawable);
            }
            d.setCallback(this);
            d.setVisible(getVisibility() == VISIBLE, false);
            d.setState(CHECKED_STATE_SET);
            setMinimumHeight(d.getIntrinsicHeight());
           
            mCheckMarkWidth = d.getIntrinsicWidth();
            setPadding(getPaddingLeft(), getPaddingTop(), mCheckMarkWidth + mBasePaddingRight, getPaddingBottom());
            d.setState(getDrawableState());
            mCheckMarkDrawable = d;
        } else {
                setPadding(getPaddingLeft(), getPaddingTop(), mBasePaddingRight, getPaddingBottom());
        }
        requestLayout();
    }
   
    @Override
    public void setPadding(int left, int top, int right, int bottom) {
        super.setPadding(left, top, right, bottom);
        mBasePaddingRight = getPaddingRight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        final Drawable checkMarkDrawable = mCheckMarkDrawable;
        if (checkMarkDrawable != null) {
            final int height = checkMarkDrawable.getIntrinsicHeight();
            int y = (getHeight() - height) / 2;
            int right = getWidth();
            checkMarkDrawable.setBounds(
                    right - mCheckMarkWidth - mBasePaddingRight,
                    y,
                    right - mBasePaddingRight,
                    y + height);
            checkMarkDrawable.draw(canvas);
        }
    }
   
    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        }
        return drawableState;
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
       
        if (mCheckMarkDrawable != null) {
            int[] myDrawableState = getDrawableState();
           
            // Set the state of the Drawable
            mCheckMarkDrawable.setState(myDrawableState);
           
            invalidate();
        }
    }

    @Override
    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
        boolean populated = super.dispatchPopulateAccessibilityEvent(event);
        if (!populated) {
            event.setChecked(mChecked);
        }
        return populated;
    }

}

2) Non resta che inizializzare la nostra applicazione con una lista che usi questa row:

Codice (XML): [Seleziona]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   >
    <ListView android:id="@+id/listView1" android:layout_width="match_parent" android:layout_height="match_parent"></ListView>
</LinearLayout>

Codice (Java): [Seleziona]
public class CustomCheckableListActivity extends ListActivity {
       
        private static final String[][] model = {
                {"Franco","Trallalla","08/03/1971"},
                {"Salvatore","Ciaobelli","00/00/0000"},
                {"Fabrizio","Butese","11/11/1111"},
                {"David","Ponsacco","22/22/2222"}};
       
       
        private LayoutInflater mInflater;
       
        @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Use an existing ListAdapter that will map an array
        // of strings to TextViews
        setListAdapter(new CustomListAdapter());
        getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        mInflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
       
        class CustomListAdapter extends BaseAdapter{

                @Override
                public int getCount() {
                        return model.length;
                }
               
                private void reuseForPosition(int position,CheckableLinearLayout cll){
                        final TextView tv1 = (TextView)cll.findViewById(R.id.textView1);
                        final TextView tv2 = (TextView)cll.findViewById(R.id.textView2);
                        final TextView tv3 = (TextView)cll.findViewById(R.id.textView3);
                       
                        tv1.setText(model[position][0]);
                        tv2.setText(model[position][1]);
                        tv3.setText(model[position][2]);
                }

                @Override
                public Object getItem(int position) {
            final View v = mInflater.inflate(R.layout.listrow, null);
                        reuseForPosition(position, (CheckableLinearLayout)v);
                        ((CheckableLinearLayout)v).setCheckMarkDrawable(android.R.drawable.btn_radio);
                        recursiveUnSetFocusable((CheckableLinearLayout)v);
                        return v;
                }

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

                @Override
                public View getView(int position, View convertView, ViewGroup parent) {
                        if (convertView==null)return (View)getItem(position);
                        reuseForPosition(position, (CheckableLinearLayout)convertView);
                        return convertView;
                }
               
                private void recursiveUnSetFocusable(ViewGroup vg)
        {
            vg.setFocusable(false);
            for(int i=0;i<vg.getChildCount();i++)
            {
                View child = vg.getChildAt(i);
                child.setFocusable(false);
                if (child instanceof  ViewGroup) recursiveUnSetFocusable((ViewGroup)child);
            }
        }
               
        }
}

Da notare che in fase di creazione degli items della lista dobbiamo settare il drawable
da usare per renderizzare il valore "checked"/"unchecked" dell'item.
Qusta operazione puo' essere ottimizzata, cosa che non ho fatto in questo piccolo snippet di codice.

ATTENZIONE: i widget che andrete ad inserire nel CheckableLinearLayout devono essere tutti
non focusabili, altrimenti l'evento di itemClick non arriveranno mai alla ListView e gli stati dei check non saranno mai aggiornati
ad ogni evento di touch. Per questo motivo ho aggiunto la funzione recursiveUnSetFocusable().

Spero tutto questo possa essere di aiuto a qualcuno di voi.
Saluti,
Franco.