Thursday, February 2, 2012

DataGrid Part 5 - Customized ListView as table

If you have not read the previous posts, you might want to start from the first post.

We will discuss the Customized ListView in this post. Customized ListView is actually the table view under the header. I use ListView to achieve this table appearance by customize it. In order to do that, we need following classes.
class cls_datagrid_adapter extends BaseAdapter {

    private Context mContext;
    private cls_datagrid.members_collection mc;
    
    public cls_datagrid_adapter(Context context, cls_datagrid.members_collection mc) {
        mContext = context;
        this.mc = mc;
    }

    public int getCount() {
        return mc.DATA_SOURCE.getRowSize();
    }

    public Object getItem(int position) {
     return mc.DATA_SOURCE.getRow(position);
    }

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

    public View getView(int position, View convertView, ViewGroup parent) {
     cls_item_view iv; 
     
     cls_datatable.cls_datarow data = mc.DATA_SOURCE.getRow(position);
     
        if(convertView == null)
        {
         iv = new cls_item_view(mContext, mc, data); 
        }
        else 
        {
         iv = (cls_item_view)convertView;
         iv.populate(data);
        }
        
        return iv;
    }
}

class cls_item_view extends LinearLayout {

 private TextView[] aryTextView;
 private Context mContext;
 private cls_spliter2_view mTxtContent;
 private cls_datagrid.members_collection mc;
 
 public cls_item_view(Context context, cls_datagrid.members_collection mc, cls_datatable.cls_datarow data) {
  super(context);
  mContext = context;
  this.mc = mc;
        
  setOrientation(HORIZONTAL);

  artTextView = new TextView[data.getColumnSize()];
  int intCellSpliter = 0;
        
  for(int i = 0; i < mc.COLUMN_STYLE.size(); i++)
  {
   aryTextView[i] = new TextView(mContext);
   aryTextView[i].setTextSize(DataCell.FontSize);
   aryTextView[i].setPadding(5,5);
   aryTextView[i].setBackgroundColor(DataCell.BackgroundColor);
   aryTextView[i].setText(data.get(mc.COLUMN_STYLE.get(i).getFieldName()));
   aryTextView[i].setSingleLine(true);
   aryTextView[i].setGravity(DataCell.Gravity);

   addView(artTextView[i], new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            
    if(intCellSpliter < mc.COLUMN_STYLE.size())
    {
     mTxtContent = new cls_spliter2_view(getContext(),mc, i);
     mc.SPLITER_VIEW.get(i).add(mTxtContent);
     addView(mTxtContent,new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
     intCellSpliter++;
    }
  }
 }
    
 public void populate(cls_datatable.cls_datarow data)
 {
  for(int i = 0; i <  mc.COLUMN_STYLE.size(); i++)
  {
   artTextView[i].setWidth(mc.COLUMN_STYLE.get(i).getWitdh() * mc.SCALE);
   artTextView[i].setText(data.get(mc.COLUMN_STYLE.get(i).getFieldName()));
  }
 }
}
Customize a ListView is quite a common task, there are a lot of examples in the Internet demonstrate this so I will briefly explain this.
  • cls_item_view takes 3 parameters in constructor. The last parameter of data typecls_datatable.cls_datarow is the record to be displayed at each row
  • All the data population happen in the foor lop in the constructor. It is similar to the initHeader(). Each TextView will be separated by a Splitter.
Back to the cls_datagrid class, we now have couple of new things to add in:
  • LIST_VIEW and DATAGRID_ADAPTER in members_collection
  • initListView() which basically initialize the ListView
  • 2 statements: initListView(); and addView(mc.LIST_VIEW); in create()
public class cls_datagrid extends LinearLayout{

 private Context mContext;
 private members_collection mc;
 private String strNoDataText;
 private LinearLayout mDataGridHeader;

 class members_collection{
  public ArrayList<column> COLUMN_STYLE;
  public cls_datatable DATA_SOURCE;
  public ListView LIST_VIEW;
  public cls_datagrid_adapter DATAGRID_ADAPTER;
 }

 public static class column{
  
  private String strColumnName;
  private String strFieldName;
  private int intWidth;
  private int intIndex;
  
  public column(String DisplayName, String FieldName, int width)
  {
   strColumnName = DisplayName;
   strFieldName = FieldName;
   intWidth = width;
  }
  
  public void setIndex(int index)
  {
   intIndex = index;
  }
  
  public int getIndex()
  {
   return intIndex;
  }
  
  public String getColumnName()
  {
   return strColumnName;
  }
  
  public String getFieldName()
  {
   return strFieldName;
  }
  
  public int getWitdh()
  {
   return intWidth;
  }
  
  public void setWidth(int width){
   intWidth = width;
  }
 }

 public cls_datagrid(Context context , AttributeSet attrs) throws Exception {        
  super(context, attrs);
  setOrientation(VERTICAL);
  mContext = context;        
  mc = new members_collection();
  mc.COLUMN_STYLE = new ArrayList<column>();
 }

 public void addColumnStyle(column columnStyle)
 {
  mc.COLUMN_STYLE.add(columnStyle);
 }

 public void setNoDataText(String strText)
 {
  strNoDataText = strText;
 }

 public void refresh()
 {
  if(mDataGridHeader == null)create();
 }

 public void create()
 {
  mDataGridHeader = new LinearLayout(mContext);
  mDataGridHeader.setOrientation(LinearLayout.HORIZONTAL);
  mc.LIST_VIEW = new ListView(mContext);

  initHeader();
  initListView();

  addView(mDataGridHeader);
  addView(mc.LIST_VIEW);
 }

 private void initHeader()
 {
  cls_spliter_view Spliter;
  cls_header_view HeaderView;
  int intCellSpliter = 0;
  
  for(int i = 0; i < mc.COLUMN_STYLE.size(); i++)
  {  
   HeaderView = new cls_header_view(getContext(), mc);
   HeaderView.setText((mc.COLUMN_STYLE.get(i)).getColumnName());
   HeaderView.setTag(mc.COLUMN_STYLE.get(i));
   HeaderView.setWidth(mc.COLUMN_STYLE.get(i).getWitdh());
   mDataGridHeader.addView(HeaderView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
      
   if(intCellSpliter < mc.COLUMN_STYLE.size())
   {
    Spliter = new cls_spliter_view(getContext(), mc, i);
    mDataGridHeader.addView(Spliter,new LinearLayout.LayoutParams(5,5));
    intCellSpliter++;
   }
  }
 }
 
 private void initListView()
 {
  mc.DATAGRID_ADAPTER = new cls_datagrid_adapter(getContext(), mc);
  mc.LIST_VIEW.setDividerHeight(2);
  mc.LIST_VIEW.setAdapter(mc.DATAGRID_ADAPTER);
 }

 public void setDataSource(cls_datatable data)
 {
  mc.DATA_SOURCE = data;
 }

}

17 comments:

  1. Hi there,

    There are some things missing here - the DataCell object being one of them. But I have many other questions (I have spent more than a day trying to figure this code out).

    Code you put up a final release so we can see it work?

    ReplyDelete
    Replies
    1. Also, I know where the one splitter class is, but where is 'cls_spliter2_view'

      Delete
    2. Hi Quintin, I just saw your message today. I feel sorry that it is difficult for you to understand the post. I realise that my works are really not up to the standard but I will try my best to improve. The code segments is messy because I copy, paste and modify from the old code.

      Please give me 2 or 3 days and I will post the complete source code where I will rewrite the whole DataGrid from scratch again. I was actually thinking to rewrite the DataGrid up to this section and post it in next article as I find it difficult to continue with the existing code as well.

      Please do visit the site in few days time. Thanks for the feedback.

      Delete
    3. Thanks HJ!

      I started off reading the posts in the wrong order - so I got really confused, but the order is rather easy to follow. I also know how it is to be a blogger and get moaned at (even though the information is free) so thank you for your effort.

      I am actually working on a Monodroid enterprise app at the moment so I would really appreciate it if I could see the code work in java, and then I know it is safe to implement in C#. To be quite honest, your posts have inspired many ideas for me to try out, so thank you once again!

      Delete
    4. I didn't have real free time in the past few days but I did manage to clean up the code a bit. You could download the code at https://docs.google.com/open?id=0B8shbq9Sw2lgYmM4Mzg5OWQtNzM0Yy00MWQ5LTkzZTEtNDY5NGViNTNmY2E3. I think I won't put this sample code in a new post for now as it is not really tidied up, to avoid such thing happen again but the sample works totally fine.

      I hope this sample could helps you and feel free to ask if you have any question. :)

      Delete
    5. Wow - I downloaded the code - it looks really fantastic! Well done!

      Delete
    6. Thanks but there are many areas need improvement. I will update the latest code in the coming post, so do check the site in the near future.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Checking out your DataGrid. Trying to sync 2 side by side DataGrids, such that left grid doesn't scroll horizontally, yet
    vertical scrolling is synced. Implemented OnScrollListener on my Activity code, and overwrote onScroll & onScrollStateChanged.

    private Boolean CanScroll1 = false;
    private Boolean CanScroll2 = false;
    private int mFirstVisibleItem = -1;

    ....
    ...

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    if(view == dg1.getMc().LIST_VIEW)
    {
    if(CanScroll2)
    dg2.getMc().LIST_VIEW.smoothScrollToPosition(mFirstVisibleItem);
    }
    else if(view == dg2.getMc().LIST_VIEW)
    {
    if(CanScroll1)
    dg1.getMc().LIST_VIEW.smoothScrollToPosition(mFirstVisibleItem);
    }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    if(scrollState != SCROLL_STATE_IDLE)
    {
    mFirstVisibleItem = view.getFirstVisiblePosition();
    //int count = view.getChildCount();
    if(view == dg1.getMc().LIST_VIEW)
    {
    CanScroll1 = false;
    CanScroll2 = true;
    }
    else if(view == dg2.getMc().LIST_VIEW)
    {
    CanScroll1 = true;
    CanScroll2 = false;
    }
    }
    else
    {
    CanScroll1 = true;
    CanScroll2 = true;
    }
    }

    Can't seem to sync the 2. I'm converting an iPhone App in which I used Daniel Tull's DataGridView to do similar. New to Android, please excuse my ignorance.

    ReplyDelete
    Replies
    1. If I understand you correctly, you want the left most column to be horizontally still and the rest of the columns remain the same, as what we could do in excel, right? Such feature had came to discussion during early development but unfortunately I didn't include it.

      I'll find free time to include it and post it up here again.

      Delete
    2. Hi, I found something useful at http://stackoverflow.com/questions/8371743/scrolling-listviews-together, while it is not a perfect solution, it does give a direction where to look into. As the table is customized ListView, the idea on that page fits well for this. Hope this helps.

      Delete
  4. This link is really Helpful. Thanks........

    ReplyDelete
  5. Thanks!!! help a lot

    ReplyDelete
  6. You may should take a look at the codeconventions.

    ReplyDelete
  7. Hi guys,
    I have tried to copy/paste each step and i get some errors, could anyone share a link to a working version?
    mail to: twisterat57@gmail.com many tx ;)

    ReplyDelete
  8. I read this tutorial and find it very interesting. however , the link does not work to download the code
    https://docs.google.com/open?id=0B8shbq9Sw2lgYmM4Mzg5OWQtNzM0Yy00MWQ5LTkzZTEtNDY5NGViNTNmY2E3

    where can i download the code?

    ReplyDelete