/* * ****************************************************************************** * Copyright (c) 2013-2014 Gabriele Mariotti. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** */ package it.gmariotti.cardslib.library.prototypes; import android.content.Context; import android.database.DataSetObserver; import android.view.LayoutInflater; import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.view.animation.AnimationUtils; import android.widget.ArrayAdapter; import java.util.ArrayList; import java.util.List; import it.gmariotti.cardslib.library.R; import it.gmariotti.cardslib.library.internal.Card; import it.gmariotti.cardslib.library.internal.CardHeader; /** * A card with a LinearList inside. * It is a particular Card that can display items inside the Card. * * @author Gabriele Mariotti (gabri.mariotti@gmail.com) */ @SuppressWarnings({"JavaDoc", "UnusedDeclaration"}) public abstract class CardWithList extends Card { /** * The cardHeader */ protected CardHeader mCardHeader; /** * The "listView" */ protected LinearListView mListView; /** * The listAdapter */ protected LinearListAdapter mLinearListAdapter; /** * Default layout used for each row */ protected int mChildLayoutId; /** * Empty View */ private View mEmptyView; /** * Progress View */ private View mProgressView; /** * Resource Id used which identifies the empty view */ protected int emptyViewId = R.id.card_inner_base_empty_cardwithlist; /** * An identifier for the layout resource to inflate when the ViewStub becomes visible */ protected int emptyViewViewStubLayoutId = R.layout.base_withlist_empty; /** * Resource Id used which identifies the progressBar */ protected int progressBarId = R.id.card_inner_base_progressbar_cardwithlist; /** * An identifier for the layout resource to inflate when the ViewStub becomes visible */ protected int progressBarViewStubLayoutId = R.layout.base_withlist_progress; /** * Indicates if the empty view feature is enabled */ protected boolean useEmptyView = true; /** * Indicates if the progressBar feature is enabled */ protected boolean useProgressBar = false; /** * Internal flag to indicate if the list is shown */ private boolean mListShown; /** * Resource Id used which identifies the list */ protected int listViewId = R.id.card_inner_base_main_cardwithlist; /** * Flag to set the observer as registered */ private boolean observerRegistered = false; private boolean couldUseNativeInnerLayout = false; private DataSetObserver mDataObserver = new DataSetObserver() { @Override public void onChanged() { internalSetupChildren(); } @Override public void onInvalidated() { internalSetupChildren(); } }; // ------------------------------------------------------------- // Constructors // ------------------------------------------------------------- /** * Constructor with a base inner layout defined by R.layout.inner_base_main_cardwithlist * * @param context context */ public CardWithList(Context context) { this(context, R.layout.inner_base_main_cardwithlist); } /** * Constructor with a custom inner layout. * * @param context context * @param innerLayout resource ID for inner layout */ public CardWithList(Context context, int innerLayout) { super(context, innerLayout); if (innerLayout == R.layout.inner_base_main_cardwithlist) { couldUseNativeInnerLayout = true; } } // ------------------------------------------------------------- // Init // ------------------------------------------------------------- /** * Init the card */ public void init() { //Init the CardHeader mCardHeader = initCardHeader(); if (mCardHeader != null) addCardHeader(mCardHeader); //Init the Card initCard(); //Init the children List<ListObject> mChildren = initChildren(); if (mChildren == null) mChildren = new ArrayList<ListObject>(); mLinearListAdapter = new LinearListAdapter(super.getContext(), mChildren); //Retrieve the layoutId use by children mChildLayoutId = getChildLayoutId(); } // ------------------------------------------------------------- // Abstract method to be implemented in your class // ------------------------------------------------------------- /** * Implement this method to initialize your CardHeader. * <p/> * An example: * <pre><code> * //Add Header * CardHeader header = new CardHeader(getContext()); * //Add a popup menu. This method set OverFlow button to visible * header.setPopupMenu(R.menu.popupmain, new CardHeader.OnClickCardHeaderPopupMenuListener() { * @Override * public void onMenuItemClick(BaseCard card, MenuItem item) { * Toast.makeText(getContext(), "Click on " + item.getTitle(), Toast.LENGTH_SHORT).show(); * } * }); * header.setTitle("Weather"); //should use R.string. * return header; * <p/> * </code></pre> * * @return the {@link CardHeader} */ protected abstract CardHeader initCardHeader(); /** * Implement this method to initialize your Card. * <p/> * An example: * <pre><code> * setSwipeable(true); * setOnSwipeListener(new OnSwipeListener() { * @Override * public void onSwipe(Card card) { * Toast.makeText(getContext(), "Swipe on " + card.getCardHeader().getTitle(), Toast.LENGTH_SHORT).show(); * } * }); * </code></pre> */ protected abstract void initCard(); /** * Implement this method to initialize the list of objects * <p/> * An example: * <pre><code> * <p/> * List<ListObject> mObjects = new ArrayList<ListObject>(); * <p/> * WeatherObject w1= new WeatherObject(); * mObjects.add(w1); * <p/> * return mObjects; * </code></pre> * * @return the List of ListObject. Return <code>null</code> if the list is empty. */ protected abstract List<ListObject> initChildren(); /** * This method is called by the {@link it.gmariotti.cardslib.library.prototypes.CardWithList.LinearListAdapter} for each row. * You can provide your layout and setup your ui elements. * * @param childPosition position inside the list of objects * @param object {@link it.gmariotti.cardslib.library.prototypes.CardWithList.ListObject} * @param convertView view used by row * @param parent parent view * @return */ public abstract View setupChildView(int childPosition, ListObject object, View convertView, ViewGroup parent); /** * Implement this method to specify the layoutId used for each row in the list * * @return the layoutId */ public abstract int getChildLayoutId(); // ------------------------------------------------------------- // View // ------------------------------------------------------------- /** * Returns the id that identifies the {@link LinearListView}. * * @return */ protected int getListViewId() { return listViewId; } @Override protected void setupInnerLayout() { //Check if the default inner layout could be the native layout if (couldUseNativeInnerLayout && isNative()) mInnerLayout = R.layout.native_inner_base_main_cardwithlist; } /** * Setup the listAdapter. * * @param parent parent view (Inner Frame) * @param view Inner View */ @Override public void setupInnerViewElements(ViewGroup parent, View view) { mListView = (LinearListView) view.findViewById(getListViewId()); if (mListView != null) { internalSetupProgressBar(parent, view); if (mLinearListAdapter != null) { internalSetupChildren(); mLinearListAdapter.registerDataSetObserver(mDataObserver); } } internalSetupEmptyView(parent, view); } /** * Setup the children */ private void internalSetupChildren() { if (mListView != null) { mListView.removeAllViews(); updateEmptyStatus((mLinearListAdapter == null) || mLinearListAdapter.isEmpty()); if (mLinearListAdapter == null) { return; } mListView.setAdapter(mLinearListAdapter); } } /** * Setup the empty view. * * @param parent mainContentLayout * @param view innerView */ @SuppressWarnings("UnusedParameters") private void internalSetupEmptyView(ViewGroup parent, View view) { if (useEmptyView) { mEmptyView = (View) parent.findViewById(getEmptyViewId()); if (mEmptyView != null) { if (mEmptyView instanceof ViewStub) ((ViewStub) mEmptyView).setLayoutResource(getEmptyViewViewStubLayoutId()); setEmptyView(mEmptyView); } } } /** * Setup the Progress Bar view. * * @param parent mainContentLayout * @param view innerView */ @SuppressWarnings("UnusedParameters") private void internalSetupProgressBar(ViewGroup parent, View view) { if (useProgressBar) { mProgressView = (View) parent.findViewById(getProgressBarId()); mListShown=true; if (mProgressView != null) { if (mProgressView instanceof ViewStub) ((ViewStub) mProgressView).setLayoutResource(getProgressBarViewStubLayoutId()); setProgressView(mProgressView); } } } /** * Use this method to unregister the observer */ public void unregisterDataSetObserver(){ if (mLinearListAdapter!=null) mLinearListAdapter.unregisterDataSetObserver(mDataObserver); } // ------------------------------------------------------------- // Interface to be used by children // ------------------------------------------------------------- /** * Children have to implement this interface */ public interface ListObject { /** * Returns the object id * * @return */ public String getObjectId(); /** * Returns the parent card */ public Card getParentCard(); /** * Register a callback to be invoked when an item in this LinearListView has * been clicked. * * @return The callback to be invoked with an item in this LinearListView has * been clicked, or null id no callback has been set. */ public void setOnItemClickListener(OnItemClickListener onItemClickListener); /** * @return The callback to be invoked with an item in this LinearListView has * been clicked, or null id no callback has been set. */ public OnItemClickListener getOnItemClickListener(); /** * Indicates if the item is swipeable */ public boolean isSwipeable(); /** * Set the item as swipeable * * @param isSwipeable */ public void setSwipeable(boolean isSwipeable); /** * Returns the callback to be invoked when item has been swiped * * @return listener */ public OnItemSwipeListener getOnItemSwipeListener(); /** * Register a callback to be invoked when an item in this LinearListView has * been swiped. * * @param onSwipeListener listener */ public void setOnItemSwipeListener(OnItemSwipeListener onSwipeListener); } // ------------------------------------------------------------- // Interface definition for a callback to be invoked when an item in this // LinearListView has been clicked. // ------------------------------------------------------------- /** * Interface definition for a callback to be invoked when an item in this * LinearListView has been clicked. */ public interface OnItemClickListener { /** * Callback method to be invoked when an item in this LinearListView has * been clicked. * <p/> * Implementers can call getItemAtPosition(position) if they need to * access the data associated with the selected item. * * @param parent The LinearListView where the click happened. * @param view The view within the LinearListView that was clicked (this * will be a view provided by the adapter) * @param position The position of the view in the adapter. * @param object The object that was clicked. */ void onItemClick(LinearListView parent, View view, int position, ListObject object); } // ------------------------------------------------------------- // On Item Swipe Interface // ------------------------------------------------------------- /** * Interface definition for a callback to be invoked when an item in this * LinearListView has been swiped */ public interface OnItemSwipeListener { /** * Callback method to be invoked when an item in this LinearListView has * been swiped. * * @param object The object that was clicked. */ public void onItemSwipe(ListObject object,boolean dismissRight); } /** * Returns listener invoked when card is swiped * * @return listener */ public OnSwipeListener getOnSwipeListener() { return mOnSwipeListener; } /** * Sets listener invoked when card is swiped. * If listener is <code>null</code> the card is not swipeable. * * @param onSwipeListener listener */ public void setOnSwipeListener(OnSwipeListener onSwipeListener) { if (onSwipeListener != null) mIsSwipeable = true; else mIsSwipeable = false; this.mOnSwipeListener = onSwipeListener; } // ------------------------------------------------------------- // Default Implementation for ListObject // ------------------------------------------------------------- /** * Default ListObject */ public class DefaultListObject implements ListObject { /** * Object Id */ protected String mObjectId; /** * Parent Card */ protected Card mParentCard; /* * Item click listener */ protected OnItemClickListener mOnItemClickListener; /** * */ protected boolean mItemSwipeable = false; /** * Item swipe listener */ protected OnItemSwipeListener mOnItemSwipeListener; // ------------------------------------------------------------- // Constructor // ------------------------------------------------------------- public DefaultListObject(Card parentCard) { this.mParentCard = parentCard; } // ------------------------------------------------------------- // Default Implementation for getters and setters // ------------------------------------------------------------- @Override public String getObjectId() { return mObjectId; } @Override public Card getParentCard() { return null; } public void setObjectId(String objectId) { mObjectId = objectId; } @Override public void setOnItemClickListener(OnItemClickListener onItemClickListener) { mOnItemClickListener = onItemClickListener; } @Override public OnItemClickListener getOnItemClickListener() { return mOnItemClickListener; } @Override public boolean isSwipeable() { return mItemSwipeable; } @Override public void setSwipeable(boolean isSwipeable) { mItemSwipeable = isSwipeable; } @Override public OnItemSwipeListener getOnItemSwipeListener() { return mOnItemSwipeListener; } @Override public void setOnItemSwipeListener(OnItemSwipeListener onItemSwipeListener) { if (onItemSwipeListener != null) mItemSwipeable = true; else mItemSwipeable = false; this.mOnItemSwipeListener = onItemSwipeListener; } } // ------------------------------------------------------------- // Empty View // ------------------------------------------------------------- /** * Sets the view to show if the adapter is empty */ public void setEmptyView(View emptyView) { mEmptyView = emptyView; useEmptyView = emptyView != null ? true : false; final LinearListAdapter adapter = getLinearListAdapter(); final boolean empty = ((adapter == null) || adapter.isEmpty()); updateEmptyStatus(empty); } /** * When the current adapter is empty, the LinearListView can display a special * view call the empty view. The empty view is used to provide feedback to * the user that no data is available in this LinearListView. * * @return The view to show if the adapter is empty. */ public View getEmptyView() { return mEmptyView; } /** * Update the status of the list based on the empty parameter. If empty is * true and we have an empty view, display it. In all the other cases, make * sure that the layout is VISIBLE and that the empty view is GONE (if * it's not null). */ private void updateEmptyStatus(boolean empty) { if (isUseEmptyView()) { if (empty) { if (mEmptyView != null) { mEmptyView.setVisibility(View.VISIBLE); mListView.setVisibility(View.GONE); } else { // If the caller just removed our empty view, make sure the list // view is visible mListView.setVisibility(View.VISIBLE); } } else { if (mEmptyView != null) mEmptyView.setVisibility(View.GONE); mListView.setVisibility(View.VISIBLE); } } } // ------------------------------------------------------------- // Progress bar // ------------------------------------------------------------- /** * When the current adapter is loading data, the LinearListView can display a special * progress Bar. * * @return The view to show if the adapter is the progress bar is enabled. */ public View getProgressView() { return mProgressView; } /** * Sets the view to show as progress bar */ public void setProgressView(View progressView) { mProgressView = progressView; useProgressBar = progressView != null; } /** * Updates the status of the list and the progress bar. * * @param shownList indicates if the list has to be shown * @param animate indicates to use an animation between view transition */ public void updateProgressBar(boolean shownList, boolean animate) { if (isUseProgressBar()) { if (mListShown == shownList) { return; } mListShown = shownList; if (shownList) { if (animate) { mProgressView.startAnimation(AnimationUtils.loadAnimation( getContext(), android.R.anim.fade_out)); mListView.startAnimation(AnimationUtils.loadAnimation( getContext(), android.R.anim.fade_in)); if (useEmptyView && mEmptyView!=null){ mEmptyView.startAnimation(AnimationUtils.loadAnimation( getContext(), android.R.anim.fade_in)); } } mProgressView.setVisibility(View.GONE); final LinearListAdapter adapter = getLinearListAdapter(); final boolean empty = ((adapter == null) || adapter.isEmpty()); updateEmptyStatus(empty); } else { if (animate) { mProgressView.startAnimation(AnimationUtils.loadAnimation( getContext(), android.R.anim.fade_in)); mListView.startAnimation(AnimationUtils.loadAnimation( getContext(), android.R.anim.fade_out)); if (useEmptyView && mEmptyView!=null){ mEmptyView.startAnimation(AnimationUtils.loadAnimation( getContext(), android.R.anim.fade_out)); } } mProgressView.setVisibility(View.VISIBLE); mListView.setVisibility(View.INVISIBLE); if (useEmptyView && mEmptyView!=null){ mEmptyView.setVisibility(View.INVISIBLE); } } } } // ------------------------------------------------------------- // Internal Adapter // ------------------------------------------------------------- /** * ListAdapter used to populate the LinearLayout inside the Card. */ @SuppressWarnings("JavaDoc") protected class LinearListAdapter extends ArrayAdapter<ListObject> { LayoutInflater mLayoutInflater; /** * Listener invoked when a card is swiped */ protected SwipeDismissListItemViewTouchListener mOnTouchListener; /** * Constructor * * @param context context * @param objects objects */ public LinearListAdapter(Context context, List<ListObject> objects) { super(context, 0, objects); //mObjects = objects; mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public View getView(final int position, View convertView, ViewGroup parent) { //Object final ListObject object = getItem(position); View view = convertView; if (view == null) { view = mLayoutInflater.inflate(getChildLayoutId(), parent, false); } //Setup the elements final View viewChild = setupChildView(position, object, view, parent); //ClickItem Listener if (viewChild != null && object.getOnItemClickListener() != null) { view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mListView.playSoundEffect(SoundEffectConstants.CLICK); object.getOnItemClickListener().onItemClick(mListView, viewChild, position, object); } }); } //SwipeItem Listener setupItemSwipeAnimation(object, viewChild); return viewChild; } @Override public int getViewTypeCount() { return 1; } /** * Sets SwipeAnimation on items List * * @param item {@link ListObject} * @param itemView itemView */ protected void setupItemSwipeAnimation(final ListObject item, View itemView) { if (item.isSwipeable()) { if (mOnTouchListener == null) { mOnTouchListener = new SwipeDismissListItemViewTouchListener(mListView, mCallback); } itemView.setOnTouchListener(mOnTouchListener); } } /** * Returns the Object Id * * @param position position in the list * @return the object Id */ @SuppressWarnings("UnusedDeclaration") public String getChildId(int position) { //Object ListObject object = getItem(position); return object.getObjectId(); } // ------------------------------------------------------------- // SwipeListener and undo action // ------------------------------------------------------------- /** * Listener invoked when a card is swiped */ SwipeDismissListItemViewTouchListener.DismissCallbacks mCallback = new SwipeDismissListItemViewTouchListener.DismissCallbacks() { @Override public boolean canDismiss(int position, Card card, ListObject listObject) { return listObject.isSwipeable(); } @Override public void onDismiss(LinearListView listView, int position, boolean dismissRight) { int i = 0; //Remove cards and notifyDataSetChanged ListObject object = getItem(position); remove(object); if (object.getOnItemSwipeListener() != null) { object.getOnItemSwipeListener().onItemSwipe(object,dismissRight); } } }; @Override public void registerDataSetObserver(DataSetObserver observer) { if (!observerRegistered) { super.registerDataSetObserver(observer); } observerRegistered = true; } @Override public void unregisterDataSetObserver(DataSetObserver observer) { if (observer == null) { observerRegistered = false; return; } super.unregisterDataSetObserver(observer); observerRegistered = false; } } // ------------------------------------------------------------- // Getter and setter // ------------------------------------------------------------- /** * Returns the adapter * * @return the adapter */ public LinearListAdapter getLinearListAdapter() { return mLinearListAdapter; } /** * Sets the adapter * * @param linearListAdapter adapter */ public void setLinearListAdapter(LinearListAdapter linearListAdapter) { mLinearListAdapter = linearListAdapter; } /** * Returns the resource Id used which identifies the empty view * * @return the resource Id used which identifies the empty view */ public int getEmptyViewId() { return emptyViewId; } /** * Sets the resource Id used which identifies the empty view * * @param emptyViewId resource Id used which identifies the empty view */ public void setEmptyViewId(int emptyViewId) { this.emptyViewId = emptyViewId; } /** * Returns the resource Id used which identifies the ProgressBar * @return the resource Id used which identifies the ProgressBar */ public int getProgressBarId() { return progressBarId; } /** * Sets the resource Id used which identifies the ProgressBar * @param progressBarId resource Id used which identifies the ProgressBar */ public void setProgressBarId(int progressBarId) { this.progressBarId = progressBarId; } /** * Return if the card uses the empty view built-in feature * * @return true if the card uses the empty view */ private boolean isUseEmptyView() { if (mEmptyView != null) return useEmptyView; else return false; } /** * Sets the flag to enable and disable the empty view feature * * @param useEmptyView flag */ public void setUseEmptyView(boolean useEmptyView) { this.useEmptyView = useEmptyView; } private boolean isUseProgressBar() { if (mProgressView != null) return useProgressBar; else return false; } /** * Sets the flag to enable and disable the progress bar * @param useProgressBar */ public void setUseProgressBar(boolean useProgressBar) { this.useProgressBar = useProgressBar; } /** * Sets the resource Id used which identifies the list * * @param listViewId resourceId which identifies the list */ public void setListViewId(int listViewId) { this.listViewId = listViewId; } /** * Returns the identifier for the layout resource to inflate when the ViewStub becomes visible * It is used only if the {@see useEmptyView) is setted to true and the {@see mEmptyView} is a {@link android.view.ViewStub}. * * @return */ public int getEmptyViewViewStubLayoutId() { return emptyViewViewStubLayoutId; } /** * Sets the identifier for the layout resource to inflate when the ViewStub becomes visible * * @param emptyViewViewStubLayoutId */ public void setEmptyViewViewStubLayoutId(int emptyViewViewStubLayoutId) { this.emptyViewViewStubLayoutId = emptyViewViewStubLayoutId; } /** * Returns the identifier for the layout resource to inflate when the ViewStub used by the ProgressBar becomes visible * It is used only if the {@see useProgressBar) is setted to true and the {@see mProgressView} is a {@link android.view.ViewStub}. * * @return */ public int getProgressBarViewStubLayoutId() { return progressBarViewStubLayoutId; } /** * Sets the identifier for the layout resource to inflate when the ViewStub used by the ProgressBar becomes visible * * @param progressBarViewStubLayoutId */ public void setProgressBarViewStubLayoutId(int progressBarViewStubLayoutId) { this.progressBarViewStubLayoutId = progressBarViewStubLayoutId; } }