package gueei.binding.widgets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import gueei.binding.AttributeBinder; import gueei.binding.Binder; import gueei.binding.BindingLog; import gueei.binding.CollectionChangedEventArg; import gueei.binding.CollectionObserver; import gueei.binding.ConstantObservable; import gueei.binding.IBindableView; import gueei.binding.IObservable; import gueei.binding.IObservableCollection; import gueei.binding.ISyntaxResolver.SyntaxResolveException; import gueei.binding.InnerFieldObservable; import gueei.binding.ViewAttribute; import gueei.binding.collections.ObservableCollection; import gueei.binding.utility.ObservableMultiplexer; import gueei.binding.utility.WeakList; import gueei.binding.viewAttributes.templates.Layout; import gueei.binding.viewAttributes.templates.LayoutItem; import gueei.binding.Observer; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; /** * BindableLinearLayout have three attributes * * * */ public class BindableLinearLayout extends LinearLayout implements IBindableView<BindableLinearLayout> { private WeakList<Object> currentList = null; private CollectionObserver collectionObserver = new CollectionObserver() { @Override public void onCollectionChanged( IObservableCollection<?> collection, CollectionChangedEventArg args, Collection<Object> initiators) { listChanged(args, collection); } }; private ObservableCollection<Object> itemList = null; private LayoutItem layout = null; private boolean updateEnabled = true; private ObservableMultiplexer<Object> observableItemsLayoutID = new ObservableMultiplexer<Object>(new Observer() { @Override public void onPropertyChanged(IObservable<?> prop, Collection<Object> initiators) { if( initiators == null || initiators.size() < 1) return; Object parent = initiators.toArray()[0]; int pos = currentList.indexOf(parent); ArrayList<Object> list = new ArrayList<Object>(); list.add(parent); removeItems(list); insertItem(pos, parent); } }); public BindableLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public BindableLinearLayout(Context context) { super(context); init(); } private void init() { } @Override protected void onDetachedFromWindow() { observableItemsLayoutID.clear(); currentList = null; super.onDetachedFromWindow(); } private void createItemSourceList(ObservableCollection<Object> newList) { if( itemList != null && collectionObserver != null) itemList.unsubscribe(collectionObserver); itemList = newList; if(newList==null) return; currentList = null; itemList.subscribe(collectionObserver); newList(newList); } private void newList(IObservableCollection<?> list) { this.removeAllViews(); observableItemsLayoutID.clear(); if( list == null) { currentList = null; return; } currentList = new WeakList<Object>(); for( int pos=0; pos < list.size(); pos ++ ) { Object item = list.getItem(pos); insertItem(pos, item); } for( int pos=0; pos < list.size(); pos ++ ) { Object item = list.getItem(pos); currentList.add(item); } } private void listChanged(CollectionChangedEventArg e, IObservableCollection<?> collection) { if( e == null) return; int pos=-1; switch( e.getAction()) { case Add: pos = e.getNewStartingIndex(); for(Object item : e.getNewItems()) { insertItem(pos, item); pos++; } break; case Remove: removeItems(e.getOldItems()); break; case Replace: removeItems(e.getOldItems()); pos = e.getNewStartingIndex(); if( pos < 0) pos=0; for(Object item : e.getNewItems()) { insertItem(pos, item); pos++; } break; case Reset: newList(collection); break; case Move: // currently the observable array list doesn't create this action throw new IllegalArgumentException("move not implemented"); default: throw new IllegalArgumentException("unknown action " + e.getAction().toString()); } if( collection == null) return; currentList = new WeakList<Object>(); for( pos=0; pos < collection.size(); pos ++ ) { Object item = collection.getItem(pos); currentList.add(item); } } private ViewAttribute<BindableLinearLayout, Object> ItemSourceAttribute = new ViewAttribute<BindableLinearLayout, Object>(Object.class, BindableLinearLayout.this, "ItemSource") { @SuppressWarnings("unchecked") @Override protected void doSetAttributeValue(Object newValue) { if( !(newValue instanceof ObservableCollection<?> )) return; if( layout != null ) createItemSourceList((ObservableCollection<Object>)newValue); } @Override public Object get() { return itemList; } }; private ViewAttribute<BindableLinearLayout, Object> ItemLayoutAttribute = new ViewAttribute<BindableLinearLayout, Object>(Object.class, BindableLinearLayout.this, "ItemLayout"){ @Override protected void doSetAttributeValue(Object newValue) { layout = null; if( newValue instanceof LayoutItem ) { layout = (LayoutItem) newValue; }else if (newValue instanceof Layout){ layout = new LayoutItem(((Layout)newValue).getDefaultLayoutId()); }else if (newValue instanceof Integer){ layout = new LayoutItem((Integer)newValue); }else{ layout = new LayoutItem(newValue.toString()); } if( itemList != null ) createItemSourceList(itemList); } @Override public Object get() { return layout; } }; private ViewAttribute<BindableLinearLayout, Boolean> ItemUpdateEnabledAttribute = new ViewAttribute<BindableLinearLayout, Boolean>(Boolean.class, BindableLinearLayout.this, "UpdateEnabled"){ @Override protected void doSetAttributeValue(Object newValue) { if( newValue == null ) { updateEnabled = true; } else if( newValue instanceof Boolean ) { Boolean value = (Boolean) newValue; updateEnabled = value; if(updateEnabled) { BindableLinearLayout.this.invalidate(); } } } @Override public Boolean get() { return updateEnabled; } }; @Override protected void onDraw(Canvas canvas) { if( !updateEnabled ) return; super.onDraw(canvas); } @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { if( !updateEnabled ) return; super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override public ViewAttribute<BindableLinearLayout, ?> createViewAttribute(String attributeId) { if (attributeId.equals("itemSource")) return ItemSourceAttribute; if (attributeId.equals("itemLayout")) return ItemLayoutAttribute; if (attributeId.equals("updateEnabled")) return ItemUpdateEnabledAttribute; return null; } private void removeItems(List<?> deleteList) { if( deleteList == null || deleteList.size() == 0 || currentList == null) return; ArrayList<Object> currentPositionList = new ArrayList<Object>(Arrays.asList(currentList.toArray())); for(Object item : deleteList){ int pos = currentPositionList.indexOf(item); observableItemsLayoutID.removeParent(item); currentPositionList.remove(item); if( pos > -1 && pos < this.getChildCount()) this.removeViewAt(pos); } } @SuppressWarnings({ "rawtypes", "unchecked" }) private void insertItem(int pos, Object item) { if( layout == null ) return; int layoutId = layout.getLayoutId(); if( layoutId < 1 && layout.getLayoutName() != null ) { IObservable<?> observable = null; InnerFieldObservable ifo = new InnerFieldObservable(layout.getLayoutName()); if (ifo.createNodes(item)) { observable = ifo; } else { Object rawField; try { rawField = Binder.getSyntaxResolver().getFieldForModel(layout.getLayoutName(), item); } catch (SyntaxResolveException e) { BindingLog.exception("BindableLinearLayout.insertItem()", e); return; } if (rawField instanceof IObservable<?>) observable = (IObservable<?>)rawField; else if (rawField!=null) observable= new ConstantObservable(rawField.getClass(), rawField); } if( observable != null) { observableItemsLayoutID.add(observable, item); Object obj = observable.get(); if(obj instanceof Integer) layoutId = (Integer)obj; } } View child = null; if( layoutId < 1 ) { TextView textView = new TextView(getContext()); textView.setText("binding error - pos: " + pos + " has no layout - please check binding:itemPath or the layout id in viewmodel"); textView.setTextColor(Color.RED); child = textView; } else { Binder.InflateResult result = Binder.inflateView(getContext(), layoutId, this, false); for(View view: result.processedViews){ AttributeBinder.getInstance().bindView(getContext(), view, item); } child = result.rootView; } this.addView(child,pos); } }