Sunday, March 18, 2012

Android: ListView, Checkable, ArrayAdapter.....FUN STUFF!!!

EDIT: I needed the list to resize based on the quanity of items. It's rather hard to do it with a ListView. So I actually skipped using the adapter and just went with building rows on a vertical LinearLayout. 

I starting to feel lazy again, so I just copy and pasted the CustomDropDown class and the necessary xml files at the bottom. But here are some notes about the code:

1. Create a CustomDropDown that extends Button. After all, the Spinner is just a button that opens a Dialog. You can copy the whole code at the bottom of this post.

- You might see something like isButtonAutoGen or something like that, setTextOnSelectedItems. These are basically related to showing the selected contents on the DropDown Button. If you look at the image below where the dropDown has text that says "Click to open dialog," then imagine that text is replaced by the values in the list. These values are separated by commas and ellipsizes if the content is too long.

- The method, createList, is where it populates the values into the drop down.

2. So now you just need the layouts for these rows and the dialog itself. I will post these at the bottom of the post as well. Animations and styles are included as well.

3. To use, make a new CustomDropDown. Populate data by passing in a text array or text resource into setAdapterFromResources. If you look at the image below, you will see a "Done" button. You may have to manual give it's clickListener the property to close the dialog.

Other things:  You can set the multichoice mode and singlechoice mode. Both modes will use the checkbox drawable, since I haven't gotten around to using a radio button for the singlechoice mode.

I hope that the code below works for you. I haven't really tested it out by copy and pasting. Anyways, farewell.

Intro:
Here is what I was trying to do with these playful things:

Goal:

Create a spinner that opens a dialog from the bottom and stops about half way. The picture shows a Single-Choice mode, but it should be able to convert to Multi-Choice mode where it shows check-boxes instead of radio buttons. Also, the contents inside the ListView must be capable of modification.

Things that need to be created:

- anim, layout, and style XMLs
- adapter, checkable, and button implementations






I'm pretty tired right now...I'll add more to this section. In the mean time, you can refer to this:
http://stackoverflow.com/questions/4842349/android-listview-with-radiobutton-checkbox-in-singlechoice-mode-and-a-custom-row

The link above helped me on implementing Checkable. This is what binds the View of a row in a ListView to have a checkable state.

Another thing if you use the Single-Choice mode for the list, using the AdapterView.OnItemSelectedListener does not work here. Instead, use AdapterView.OnItemClickListner. This means if you want another component to be affected by the dropdown at the beginning of the activity, you would have to manually set the state of the component. Then the listener will take care of the rest afterwards.

When access data in the ListView, such as the checkbox value, all ways use the "Item Checked" methods. The Item Selected just does not work at all. So use those methods for both Singe and Multi-modes.

Anyways, time to flee......

Full CustomDropDown code:
public class CustomDropDown extends Button {

 private Context context;
 private Dialog dialog;
 private boolean isButtonTextAutoGen;
 private CheckBox[] checkBox;
 private TextView[] textView;
 private int listSize;
 private ListLayout layout;
 
 private final int TEXT_SIZE = 16;

 public CustomDropDown(Context context, AttributeSet attrs) {
  super(context, attrs);
  setBackgroundDrawable(getResources().getDrawable(android.R.drawable.btn_dropdown));
  setGravity(Gravity.LEFT|Gravity.CENTER_VERTICAL);
  this.setEllipsize(TextUtils.TruncateAt.END);
  this.setSingleLine(true);
  this.context = context;
  this.isButtonTextAutoGen = true;
 }
 
 // Adapter methods
 
 public void setAdapterFromResources(int array_res) {
  CharSequence[] txt = context.getResources().getTextArray(array_res);
  setAdapterFromTextArray(txt);  
 }
 
 public void setAdapterFromTextArray(CharSequence[] txt) {
  
  // Initialize dialog
  this.dialog = new Dialog(context,R.style.Popup_Dialog) {
   @Override
   public void onDetachedFromWindow() {
    if (isButtonTextAutoGen) setTextOnButtonToSelectedItems();
   }
  };
  this.dialog.setContentView(R.layout.popup_list_dialog); 
  this.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    dialog.show(); 
   }
  });

  createList(txt);
 }
 
 private void createList(CharSequence[] text) {
  
  this.listSize = text.length;
  textView = new TextView[listSize];
  checkBox = new CheckBox[listSize];
  
  layout = (ListLayout) dialog.findViewById(R.id.listlayout);
  
  for (int i=0;i<listSize;i++) {
   View row = LayoutInflater.from(getContext()).inflate(R.layout.single_line_checkbox_item, null);
   textView[i] = (TextView) row.findViewById(R.id.text);
   checkBox[i] = (CheckBox) row.findViewById(R.id.checkbox);
   
   textView[i].setText(text[i].toString());
   textView[i].setTextSize(TypedValue.COMPLEX_UNIT_PX,TEXT_SIZE);
   
   layout.addView(row);
   
   if (i < (listSize-1)) {
    LinearLayout divider = new LinearLayout(getContext());
    divider.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 1));
    divider.setBackgroundColor(Color.argb(160, 160, 160, 160));
    
    layout.addView(divider);
   }
  }
  
  layout.setChoiceMode(ListLayout.CHOICE_MODE_MULTIPLE);
  
 }
 
 // Getters/setters
 public Dialog getDialog()     { return this.dialog; }
 
 public void setBoolStringToCheckBoxes(String s) {
  if (s!=null) 
  {
   for (int i=0;i<s.length();i++) {
    if (s.charAt(i)=='1') checkBox[i].setChecked(true);
    else checkBox[i].setChecked(false);
   }
   
  }
 }
 
 public String getCheckBoxValuesToString() {
  StringBuilder sb = new StringBuilder(); 
  for (int i=0;i<listSize;i++) {
   if (checkBox[i].isChecked()) sb.append("1");
   else sb.append("0");
  }
  return sb.toString();
 }
 
 public boolean isNoneChecked() {
  for (int i=0;i<listSize;i++) 
   if (checkBox[i].isChecked()) return false;
  return true;
 }
 
 public boolean isCheckedAt(int index) {
  return checkBox[index].isChecked();
 }
 
 public int getSize() { return listSize; }
 
 public void setTextAt(int index, String text) {
  textView[index].setText(text);
 }
 
 public String getTextAt(int index) { 
  return textView[index].getText().toString();
 }
 
 public CheckBox[] getCheckBoxArray() { return checkBox; }
 
 public void singleChoiceListenerMethod(CheckBox chbx) {
  for (int i=0;i<listSize;i++) {
   final int current = i;
   for (CheckBox b: checkBox) {
    if (b==chbx) b.setChecked(true);
    else b.setChecked(false);
   }
  }
 }
 
 public void setChoiceMode(int mode) {
  setChoiceMode(mode,0);
 }
 
 public void setChoiceMode(int mode, int startingPos) {
  if (mode==ListView.CHOICE_MODE_SINGLE) {
   
   ((TextView) dialog.findViewById(R.id.select_option_text)).setText("Select one");
   
   // set up listeners to act like single mode
   for (int i=0;i<listSize;i++) {
    final int current = i;
    checkBox[i].setOnClickListener(new View.OnClickListener() {     
     @Override
     public void onClick(View v) {      
      for (CheckBox b: checkBox) {
       if (b==checkBox[current]) b.setChecked(true);
       else b.setChecked(false);
      }
     }
    });
   }
   // check if none is check at start, set the first value to true
   
//   for (int i=0;i<listSize;i++) {
//    if (checkBox[i].isChecked()) return;
//   }
//   checkBox[startingPos].setChecked(true);
  }
 }
 
 public int getSelectedItemPosition() {
  for (int i=0;i<listSize;i++) {
   if (checkBox[0].isChecked()) return i;
  }
  return -1;
 }
 
 public void setTextOnButtonToSelectedItems() {
  StringBuilder sb = new StringBuilder();
  for (int i=0;i<listSize;i++) {
   if (checkBox[i].isChecked()) sb.append(getTextAt(i)).append(", ");
  }
  if (sb.length()!=0) {
   this.setText(sb.toString().substring(0, sb.length()-2));
  } else {
   this.setText("--Select--");
  }
 }
 
 public void setIsButtonTextAutoGen(boolean b) { this.isButtonTextAutoGen = b; }

}

popup_list_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@color/gray"
    android:layout_gravity="bottom"
    android:padding="5dp"
    >
 <RelativeLayout 
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     >
     <TextView
         android:id="@+id/select_option_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_centerVertical="true"
         android:text="Select one or more"
         android:textColor="@color/black"
         />
     <Button        
        android:id="@+id/btn_done"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="  Done  " 
        android:layout_alignParentRight="true"    
        />
 </RelativeLayout>
    
    <com.att.planitgreen.component.ListLayout 
        android:id="@+id/listlayout"        
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"   
        android:orientation="vertical"     
        android:background="#666666"
        android:padding="5dp"
        />
</LinearLayout>

single_line_checkbox_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
   >
    <TextView
        android:id="@+id/text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_toLeftOf="@+id/checkbox"
        android:text="text"
        />
    <CheckBox 
        android:id="@+id/checkbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        />

</RelativeLayout>

styles.xml
<style name="Popup_Dialog">
        <item name="android:windowNoTitle">true</item>  
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowContentOverlay">@null</item>  
        <item name="android:windowAnimationStyle">@style/Animations.PopupDialog</item>
        
    </style>

<style name="Animations" parent="@android:Animation"/>  
     <style name="Animations.PopupDialog">
         <item name="android:windowEnterAnimation">@anim/slide_in_bottom</item>  
            <item name="android:windowExitAnimation">@anim/slide_out_down</item>
     </style>

slide_in_bottom.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
 <translate android:fromYDelta="100%" android:toYDelta="0"
            android:duration="@android:integer/config_mediumAnimTime"/>
</set>

slide_out_down.xml
<?xml version="1.0" encoding="utf-8"?>
<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromYDelta="0"
    android:toYDelta="100%p"
    android:duration="@android:integer/config_mediumAnimTime" 
    
    />

No comments:

Post a Comment