+ *
+ * A listener will be attached to the button which will
+ * either expand or collapse the expandable view
+ *
+ * @see #getExpandableView(View)
+ * @param parent the list view item
+ * @ensure return!=null
+ * @return a child of parent which is a button
+ */
+ public abstract View getExpandToggleButton(View parent);
+
+ /**
+ * This method is used to get the view that will be hidden
+ * initially and expands or collapse when the ExpandToggleButton
+ * is pressed @see getExpandToggleButton
+ *
+ * Normally it will be implemented as:
+ *
+ */
+public class BaseAdapterHelper {
+
+ /** Views indexed with their IDs */
+ private final SparseArray views;
+
+ private final Context context;
+
+ private int position;
+
+ private View convertView;
+
+ /** Package private field to retain the associated user object and detect a change */
+ Object associatedObject;
+
+ protected BaseAdapterHelper(Context context, ViewGroup parent, int layoutId, int position) {
+ this.context = context;
+ this.position = position;
+ this.views = new SparseArray();
+ convertView = LayoutInflater.from(context) //
+ .inflate(layoutId, parent, false);
+ convertView.setTag(this);
+ }
+
+ /**
+ * This method is the only entry point to get a BaseAdapterHelper.
+ * @param context The current context.
+ * @param convertView The convertView arg passed to the getView() method.
+ * @param parent The parent arg passed to the getView() method.
+ * @return A BaseAdapterHelper instance.
+ */
+ public static BaseAdapterHelper get(Context context, View convertView, ViewGroup parent, int layoutId) {
+ return get(context, convertView, parent, layoutId, -1);
+ }
+
+ /** This method is package private and should only be used by QuickAdapter. */
+ static BaseAdapterHelper get(Context context, View convertView, ViewGroup parent, int layoutId, int position) {
+ if (convertView == null) {
+ return new BaseAdapterHelper(context, parent, layoutId, position);
+ }
+
+ // Retrieve the existing helper and update its position
+ BaseAdapterHelper existingHelper = (BaseAdapterHelper) convertView.getTag();
+ existingHelper.position = position;
+ return existingHelper;
+ }
+
+ /**
+ * This method allows you to retrieve a view and perform custom
+ * operations on it, not covered by the BaseAdapterHelper.
+ * If you think it's a common use case, please consider creating
+ * a new issue at https://github.com/JoanZapata/base-adapter-helper/issues.
+ * @param viewId The id of the view you want to retrieve.
+ */
+ public T getView(int viewId) {
+ return retrieveView(viewId);
+ }
+
+ /**
+ * Will set the text of a TextView.
+ * @param viewId The view id.
+ * @param value The text to put in the text view.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setText(int viewId, String value) {
+ TextView view = retrieveView(viewId);
+ view.setText(value);
+ return this;
+ }
+
+ /**
+ * Will set the image of an ImageView from a resource id.
+ * @param viewId The view id.
+ * @param imageResId The image resource id.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setImageResource(int viewId, int imageResId) {
+ ImageView view = retrieveView(viewId);
+ view.setImageResource(imageResId);
+ return this;
+ }
+
+ /**
+ * Will set background color of a view.
+ * @param viewId The view id.
+ * @param color A color, not a resource id.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setBackgroundColor(int viewId, int color) {
+ View view = retrieveView(viewId);
+ view.setBackgroundColor(color);
+ return this;
+ }
+
+ /**
+ * Will set background of a view.
+ * @param viewId The view id.
+ * @param backgroundRes A resource to use as a background.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setBackgroundRes(int viewId, int backgroundRes) {
+ View view = retrieveView(viewId);
+ view.setBackgroundResource(backgroundRes);
+ return this;
+ }
+
+ /**
+ * Will set text color of a TextView.
+ * @param viewId The view id.
+ * @param textColor The text color (not a resource id).
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setTextColor(int viewId, int textColor) {
+ TextView view = retrieveView(viewId);
+ view.setTextColor(textColor);
+ return this;
+ }
+
+ /**
+ * Will set text color of a TextView.
+ * @param viewId The view id.
+ * @param textColorRes The text color resource id.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setTextColorRes(int viewId, int textColorRes) {
+ TextView view = retrieveView(viewId);
+ view.setTextColor(context.getResources().getColor(textColorRes));
+ return this;
+ }
+
+ /**
+ * Will set the image of an ImageView from a drawable.
+ * @param viewId The view id.
+ * @param drawable The image drawable.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setImageDrawable(int viewId, Drawable drawable) {
+ ImageView view = retrieveView(viewId);
+ view.setImageDrawable(drawable);
+ return this;
+ }
+
+ /**
+ * Will download an image from a URL and put it in an ImageView.
+ * It uses Square's Picasso library to download the image asynchronously and put the result into the ImageView.
+ * Picasso manages recycling of views in a ListView.
+ * If you need more control over the Picasso settings, use {BaseAdapterHelper#setImageBuilder}.
+ * @param viewId The view id.
+ * @param imageUrl The image URL.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setImageUrl(int viewId, String imageUrl) {
+ ImageView view = retrieveView(viewId);
+ Picasso.with(context).load(imageUrl).into(view);
+ return this;
+ }
+
+ /**
+ * Will download an image from a URL and put it in an ImageView.
+ * @param viewId The view id.
+ * @param requestBuilder The Picasso request builder. (e.g. Picasso.with(context).load(imageUrl))
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setImageBuilder(int viewId, RequestCreator requestBuilder) {
+ ImageView view = retrieveView(viewId);
+ requestBuilder.into(view);
+ return this;
+ }
+
+ /** Add an action to set the image of an image view. Can be called multiple times. */
+ public BaseAdapterHelper setImageBitmap(int viewId, Bitmap bitmap) {
+ ImageView view = retrieveView(viewId);
+ view.setImageBitmap(bitmap);
+ return this;
+ }
+
+ /**
+ * Add an action to set the alpha of a view. Can be called multiple times.
+ * Alpha between 0-1.
+ */
+ public BaseAdapterHelper setAlpha(int viewId, float value) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ retrieveView(viewId).setAlpha(value);
+ } else {
+ // Pre-honeycomb hack to set Alpha value
+ AlphaAnimation alpha = new AlphaAnimation(value, value);
+ alpha.setDuration(0);
+ alpha.setFillAfter(true);
+ retrieveView(viewId).startAnimation(alpha);
+ }
+ return this;
+ }
+
+ /**
+ * Set a view visibility to VISIBLE (true) or GONE (false).
+ * @param viewId The view id.
+ * @param visible True for VISIBLE, false for GONE.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setVisible(int viewId, boolean visible) {
+ View view = retrieveView(viewId);
+ view.setVisibility(visible ? View.VISIBLE : View.GONE);
+ return this;
+ }
+
+ /**
+ * Add links into a TextView.
+ * @param viewId The id of the TextView to linkify.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper linkify(int viewId) {
+ TextView view = retrieveView(viewId);
+ Linkify.addLinks(view, Linkify.ALL);
+ return this;
+ }
+
+ /** Apply the typeface to the given viewId, and enable subpixel rendering. */
+ public BaseAdapterHelper setTypeface(int viewId, Typeface typeface) {
+ TextView view = retrieveView(viewId);
+ view.setTypeface(typeface);
+ view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
+ return this;
+ }
+
+ /** Apply the typeface to all the given viewIds, and enable subpixel rendering. */
+ public BaseAdapterHelper setTypeface(Typeface typeface, int... viewIds) {
+ for (int viewId : viewIds) {
+ TextView view = retrieveView(viewId);
+ view.setTypeface(typeface);
+ view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the progress of a ProgressBar.
+ * @param viewId The view id.
+ * @param progress The progress.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setProgress(int viewId, int progress) {
+ ProgressBar view = retrieveView(viewId);
+ view.setProgress(progress);
+ return this;
+ }
+
+ /**
+ * Sets the progress and max of a ProgressBar.
+ * @param viewId The view id.
+ * @param progress The progress.
+ * @param max The max value of a ProgressBar.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setProgress(int viewId, int progress, int max) {
+ ProgressBar view = retrieveView(viewId);
+ view.setMax(max);
+ view.setProgress(progress);
+ return this;
+ }
+
+ /**
+ * Sets the range of a ProgressBar to 0...max.
+ * @param viewId The view id.
+ * @param max The max value of a ProgressBar.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setMax(int viewId, int max) {
+ ProgressBar view = retrieveView(viewId);
+ view.setMax(max);
+ return this;
+ }
+
+ /**
+ * Sets the rating (the number of stars filled) of a RatingBar.
+ * @param viewId The view id.
+ * @param rating The rating.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setRating(int viewId, float rating) {
+ RatingBar view = retrieveView(viewId);
+ view.setRating(rating);
+ return this;
+ }
+
+ /**
+ * Sets the rating (the number of stars filled) and max of a RatingBar.
+ * @param viewId The view id.
+ * @param rating The rating.
+ * @param max The range of the RatingBar to 0...max.
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setRating(int viewId, float rating, int max) {
+ RatingBar view = retrieveView(viewId);
+ view.setMax(max);
+ view.setRating(rating);
+ return this;
+ }
+
+ /**
+ * Sets the on click listener of the view.
+ * @param viewId The view id.
+ * @param listener The on click listener;
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setOnClickListener(int viewId, View.OnClickListener listener) {
+ View view = retrieveView(viewId);
+ view.setOnClickListener(listener);
+ return this;
+ }
+
+ /**
+ * Sets the on touch listener of the view.
+ * @param viewId The view id.
+ * @param listener The on touch listener;
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setOnTouchListener(int viewId, View.OnTouchListener listener) {
+ View view = retrieveView(viewId);
+ view.setOnTouchListener(listener);
+ return this;
+ }
+
+ /**
+ * Sets the on long click listener of the view.
+ * @param viewId The view id.
+ * @param listener The on long click listener;
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setOnLongClickListener(int viewId, View.OnLongClickListener listener) {
+ View view = retrieveView(viewId);
+ view.setOnLongClickListener(listener);
+ return this;
+ }
+
+ /**
+ * Sets the tag of the view.
+ * @param viewId The view id.
+ * @param tag The tag;
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setTag(int viewId, Object tag) {
+ View view = retrieveView(viewId);
+ view.setTag(tag);
+ return this;
+ }
+
+ /**
+ * Sets the tag of the view.
+ * @param viewId The view id.
+ * @param key The key of tag;
+ * @param tag The tag;
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setTag(int viewId, int key, Object tag) {
+ View view = retrieveView(viewId);
+ view.setTag(key, tag);
+ return this;
+ }
+
+ /**
+ * Sets the checked status of a checkable.
+ * @param viewId The view id.
+ * @param checked The checked status;
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setChecked(int viewId, boolean checked) {
+ Checkable view = (Checkable) retrieveView(viewId);
+ view.setChecked(checked);
+ return this;
+ }
+
+ /**
+ * Sets the adapter of a adapter view.
+ * @param viewId The view id.
+ * @param adapter The adapter;
+ * @return The BaseAdapterHelper for chaining.
+ */
+ public BaseAdapterHelper setAdapter(int viewId, Adapter adapter) {
+ AdapterView view = retrieveView(viewId);
+ view.setAdapter(adapter);
+ return this;
+ }
+
+ /** Retrieve the convertView */
+ public View getView() {
+ return convertView;
+ }
+
+ /**
+ * Retrieve the overall position of the data in the list.
+ * @throws IllegalArgumentException If the position hasn't been set at the construction of the this helper.
+ */
+ public int getPosition() {
+ if (position == -1)
+ throw new IllegalStateException("Use BaseAdapterHelper constructor " +
+ "with position if you need to retrieve the position.");
+ return position;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected T retrieveView(int viewId) {
+ View view = views.get(viewId);
+ if (view == null) {
+ view = convertView.findViewById(viewId);
+ views.put(viewId, view);
+ }
+ return (T) view;
+ }
+
+ /** Retrieves the last converted object on this view. */
+ public Object getAssociatedObject() {
+ return associatedObject;
+ }
+
+ /** Should be called during convert */
+ public void setAssociatedObject(Object associatedObject) {
+ this.associatedObject = associatedObject;
+ }
+}
diff --git a/base-adapter-helper-demo/src/com/joanzapata/android/BaseQuickAdapter.java b/base-adapter-helper-demo/src/com/joanzapata/android/BaseQuickAdapter.java
new file mode 100644
index 0000000..06e692f
--- /dev/null
+++ b/base-adapter-helper-demo/src/com/joanzapata/android/BaseQuickAdapter.java
@@ -0,0 +1,197 @@
+/**
+ * Copyright 2013 Joan Zapata
+ *
+ * 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 com.joanzapata.android;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Abstraction class of a BaseAdapter in which you only need
+ * to provide the convert() implementation.
+ * Using the provided BaseAdapterHelper, your code is minimalist.
+ * @param The type of the items in the list.
+ */
+public abstract class BaseQuickAdapter extends BaseAdapter {
+
+ protected static final String TAG = BaseQuickAdapter.class.getSimpleName();
+
+ protected final Context context;
+
+ protected final int layoutResId;
+
+ protected final List data;
+
+ protected boolean displayIndeterminateProgress = false;
+
+ /**
+ * Create a QuickAdapter.
+ * @param context The context.
+ * @param layoutResId The layout resource id of each item.
+ */
+ public BaseQuickAdapter(Context context, int layoutResId) {
+ this(context, layoutResId, null);
+ }
+
+ /**
+ * Same as QuickAdapter#QuickAdapter(Context,int) but with
+ * some initialization data.
+ * @param context The context.
+ * @param layoutResId The layout resource id of each item.
+ * @param data A new list is created out of this one to avoid mutable list
+ */
+ public BaseQuickAdapter(Context context, int layoutResId, List data) {
+ this.data = data == null ? new ArrayList() : new ArrayList(data);
+ this.context = context;
+ this.layoutResId = layoutResId;
+ }
+
+ @Override
+ public int getCount() {
+ int extra = displayIndeterminateProgress ? 1 : 0;
+ return data.size() + extra;
+ }
+
+ @Override
+ public T getItem(int position) {
+ if (position >= data.size()) return null;
+ return data.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position >= data.size() ? 1 : 0;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (getItemViewType(position) == 0) {
+ final H helper = getAdapterHelper(position, convertView, parent);
+ T item = getItem(position);
+ helper.setAssociatedObject(item);
+ convert(helper, item);
+ return helper.getView();
+ }
+
+ return createIndeterminateProgressView(convertView, parent);
+ }
+
+ private View createIndeterminateProgressView(View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ FrameLayout container = new FrameLayout(context);
+ container.setForegroundGravity(Gravity.CENTER);
+ ProgressBar progress = new ProgressBar(context);
+ container.addView(progress);
+ convertView = container;
+ }
+ return convertView;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return position < data.size();
+ }
+
+ public void add(T elem) {
+ data.add(elem);
+ notifyDataSetChanged();
+ }
+
+ public void addAll(List elem) {
+ data.addAll(elem);
+ notifyDataSetChanged();
+ }
+
+ public void set(T oldElem, T newElem) {
+ set(data.indexOf(oldElem), newElem);
+ }
+
+ public void set(int index, T elem) {
+ data.set(index, elem);
+ notifyDataSetChanged();
+ }
+
+ public void remove(T elem) {
+ data.remove(elem);
+ notifyDataSetChanged();
+ }
+
+ public void remove(int index) {
+ data.remove(index);
+ notifyDataSetChanged();
+ }
+
+ public void replaceAll(List elem) {
+ data.clear();
+ data.addAll(elem);
+ notifyDataSetChanged();
+ }
+
+ public boolean contains(T elem) {
+ return data.contains(elem);
+ }
+
+ /** Clear data list */
+ public void clear() {
+ data.clear();
+ notifyDataSetChanged();
+ }
+
+ public void showIndeterminateProgress(boolean display) {
+ if (display == displayIndeterminateProgress) return;
+ displayIndeterminateProgress = display;
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Implement this method and use the helper to adapt the view to the given item.
+ * @param helper A fully initialized helper.
+ * @param item The item that needs to be displayed.
+ */
+ protected abstract void convert(H helper, T item);
+
+ /**
+ * You can override this method to use a custom BaseAdapterHelper in order to fit your needs
+ * @param position The position of the item within the adapter's data set of the item whose view we want.
+ * @param convertView The old view to reuse, if possible. Note: You should check that this view
+ * is non-null and of an appropriate type before using. If it is not possible to convert
+ * this view to display the correct data, this method can create a new view.
+ * Heterogeneous lists can specify their number of view types, so that this View is
+ * always of the right type (see {@link #getViewTypeCount()} and
+ * {@link #getItemViewType(int)}).
+ * @param parent The parent that this view will eventually be attached to
+ * @return An instance of BaseAdapterHelper
+ */
+ protected abstract H getAdapterHelper(int position, View convertView, ViewGroup parent);
+
+}
diff --git a/base-adapter-helper-demo/src/com/joanzapata/android/EnhancedQuickAdapter.java b/base-adapter-helper-demo/src/com/joanzapata/android/EnhancedQuickAdapter.java
new file mode 100644
index 0000000..49e434c
--- /dev/null
+++ b/base-adapter-helper-demo/src/com/joanzapata/android/EnhancedQuickAdapter.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright 2013 Joan Zapata
+ *
+ * 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 com.joanzapata.android;
+
+import android.content.Context;
+
+import java.util.List;
+
+/**
+ * Same as QuickAdapter, but adds an "itemChanged" boolean in the
+ * convert() method params, which allows you to know if you are
+ * adapting the new view to the same item or not, and therefore
+ * make a difference between dataset changed / dataset invalidated.
+ *
+ * Abstraction class of a BaseAdapter in which you only need
+ * to provide the convert() implementation.
+ * Using the provided BaseAdapterHelper, your code is minimalist.
+ * @param The type of the items in the list.
+ */
+public abstract class EnhancedQuickAdapter extends QuickAdapter {
+
+ /**
+ * Create a QuickAdapter.
+ * @param context The context.
+ * @param layoutResId The layout resource id of each item.
+ */
+ public EnhancedQuickAdapter(Context context, int layoutResId) {
+ super(context, layoutResId);
+ }
+
+ /**
+ * Same as QuickAdapter#QuickAdapter(Context,int) but with
+ * some initialization data.
+ * @param context The context.
+ * @param layoutResId The layout resource id of each item.
+ * @param data A new list is created out of this one to avoid mutable list
+ */
+ public EnhancedQuickAdapter(Context context, int layoutResId, List data) {
+ super(context, layoutResId, data);
+ }
+
+ @Override
+ protected final void convert(BaseAdapterHelper helper, T item) {
+ boolean itemChanged = helper.associatedObject == null || !helper.associatedObject.equals(item);
+ helper.associatedObject = item;
+ convert(helper, item, itemChanged);
+ }
+
+ /**
+ * @param helper The helper to use to adapt the view.
+ * @param item The item you should adapt the view to.
+ * @param itemChanged Whether or not the helper was bound to another object before.
+ */
+ protected abstract void convert(BaseAdapterHelper helper, T item, boolean itemChanged);
+}
diff --git a/base-adapter-helper-demo/src/com/joanzapata/android/QuickAdapter.java b/base-adapter-helper-demo/src/com/joanzapata/android/QuickAdapter.java
new file mode 100644
index 0000000..f37ec78
--- /dev/null
+++ b/base-adapter-helper-demo/src/com/joanzapata/android/QuickAdapter.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright 2013 Joan Zapata
+ *
+ * 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 com.joanzapata.android;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+import static com.joanzapata.android.BaseAdapterHelper.get;
+
+/**
+ * Abstraction class of a BaseAdapter in which you only need
+ * to provide the convert() implementation.
+ * Using the provided BaseAdapterHelper, your code is minimalist.
+ * @param The type of the items in the list.
+ */
+public abstract class QuickAdapter extends BaseQuickAdapter {
+
+ /**
+ * Create a QuickAdapter.
+ * @param context The context.
+ * @param layoutResId The layout resource id of each item.
+ */
+ public QuickAdapter(Context context, int layoutResId) {
+ super(context, layoutResId);
+ }
+
+ /**
+ * Same as QuickAdapter#QuickAdapter(Context,int) but with
+ * some initialization data.
+ * @param context The context.
+ * @param layoutResId The layout resource id of each item.
+ * @param data A new list is created out of this one to avoid mutable list
+ */
+ public QuickAdapter(Context context, int layoutResId, List data) {
+ super(context, layoutResId, data);
+ }
+
+ protected BaseAdapterHelper getAdapterHelper(int position, View convertView, ViewGroup parent) {
+ return get(context, convertView, parent, layoutResId, position);
+ }
+
+}
diff --git a/calendar-list-view-demo/.gitignore b/calendar-list-view-demo/.gitignore
new file mode 100644
index 0000000..afbdab3
--- /dev/null
+++ b/calendar-list-view-demo/.gitignore
@@ -0,0 +1,6 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
diff --git a/calendar-list-view-demo/CalendarListView-Demo.iml b/calendar-list-view-demo/CalendarListView-Demo.iml
new file mode 100644
index 0000000..2a02201
--- /dev/null
+++ b/calendar-list-view-demo/CalendarListView-Demo.iml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/calendar-list-view-demo/README.md b/calendar-list-view-demo/README.md
new file mode 100644
index 0000000..12a036d
--- /dev/null
+++ b/calendar-list-view-demo/README.md
@@ -0,0 +1,6 @@
+calendar-list-view-demo
+============
+### 1.Demo Download
+[本地下载](apk/calendar-list-view-demo.apk?raw=true "点击下载到本地")
+###2. Screenshot
+
\ No newline at end of file
diff --git a/calendar-list-view-demo/apk/calendar-list-view-demo.apk b/calendar-list-view-demo/apk/calendar-list-view-demo.apk
new file mode 100644
index 0000000..f9d74d8
Binary files /dev/null and b/calendar-list-view-demo/apk/calendar-list-view-demo.apk differ
diff --git a/calendar-list-view-demo/apk/calendar-list-view-demo.gif b/calendar-list-view-demo/apk/calendar-list-view-demo.gif
new file mode 100644
index 0000000..49be658
Binary files /dev/null and b/calendar-list-view-demo/apk/calendar-list-view-demo.gif differ
diff --git a/calendar-list-view-demo/apk/manifest-merger-release-report.txt b/calendar-list-view-demo/apk/manifest-merger-release-report.txt
new file mode 100644
index 0000000..b103beb
--- /dev/null
+++ b/calendar-list-view-demo/apk/manifest-merger-release-report.txt
@@ -0,0 +1,65 @@
+-- Merging decision tree log ---
+manifest
+ADDED from AndroidManifest.xml:2:1
+ package
+ ADDED from AndroidManifest.xml:3:5
+ INJECTED from AndroidManifest.xml:0:0
+ INJECTED from AndroidManifest.xml:0:0
+ android:versionName
+ INJECTED from AndroidManifest.xml:0:0
+ INJECTED from AndroidManifest.xml:0:0
+ xmlns:android
+ ADDED from AndroidManifest.xml:2:11
+ android:versionCode
+ INJECTED from AndroidManifest.xml:0:0
+ INJECTED from AndroidManifest.xml:0:0
+application
+ADDED from AndroidManifest.xml:5:5
+MERGED from calendar-list-view-demo:library:unspecified:12:5
+MERGED from com.android.support:appcompat-v7:21.0.0:16:5
+MERGED from com.android.support:support-v4:21.0.0:16:5
+MERGED from com.android.support:recyclerview-v7:21.0.0:17:5
+MERGED from com.android.support:support-v4:21.0.0:16:5
+ android:label
+ ADDED from AndroidManifest.xml:8:9
+ android:allowBackup
+ ADDED from AndroidManifest.xml:6:9
+ android:icon
+ ADDED from AndroidManifest.xml:7:9
+ android:theme
+ ADDED from AndroidManifest.xml:9:9
+activity#com.rogary.calendarlistviewdemo.CalandarDemoActivity
+ADDED from AndroidManifest.xml:10:9
+ android:label
+ ADDED from AndroidManifest.xml:12:13
+ android:name
+ ADDED from AndroidManifest.xml:11:13
+intent-filter#android.intent.action.MAIN+android.intent.category.LAUNCHER
+ADDED from AndroidManifest.xml:13:13
+action#android.intent.action.MAIN
+ADDED from AndroidManifest.xml:14:17
+ android:name
+ ADDED from AndroidManifest.xml:14:25
+category#android.intent.category.LAUNCHER
+ADDED from AndroidManifest.xml:16:17
+ android:name
+ ADDED from AndroidManifest.xml:16:27
+uses-sdk
+INJECTED from AndroidManifest.xml:0:0 reason: use-sdk injection requested
+MERGED from calendar-list-view-demo:library:unspecified:8:5
+MERGED from com.android.support:appcompat-v7:21.0.0:15:5
+MERGED from com.android.support:support-v4:21.0.0:15:5
+MERGED from com.android.support:recyclerview-v7:21.0.0:15:5
+MERGED from com.android.support:support-v4:21.0.0:15:5
+ android:targetSdkVersion
+ INJECTED from AndroidManifest.xml:0:0
+ INJECTED from AndroidManifest.xml:0:0
+ android:minSdkVersion
+ INJECTED from AndroidManifest.xml:0:0
+ INJECTED from AndroidManifest.xml:0:0
+activity#android.support.v7.widget.TestActivity
+ADDED from com.android.support:recyclerview-v7:21.0.0:18:9
+ android:label
+ ADDED from com.android.support:recyclerview-v7:21.0.0:18:19
+ android:name
+ ADDED from com.android.support:recyclerview-v7:21.0.0:18:60
diff --git a/calendar-list-view-demo/app/.gitignore b/calendar-list-view-demo/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/calendar-list-view-demo/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/calendar-list-view-demo/app/app.iml b/calendar-list-view-demo/app/app.iml
new file mode 100644
index 0000000..eb3f07d
--- /dev/null
+++ b/calendar-list-view-demo/app/app.iml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/calendar-list-view-demo/app/build.gradle b/calendar-list-view-demo/app/build.gradle
new file mode 100644
index 0000000..7044ea3
--- /dev/null
+++ b/calendar-list-view-demo/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ applicationId "com.rogary.calendarlistviewdemo"
+ minSdkVersion 14
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+ compile project(':library')
+}
diff --git a/calendar-list-view-demo/app/proguard-rules.pro b/calendar-list-view-demo/app/proguard-rules.pro
new file mode 100644
index 0000000..f13cc3c
--- /dev/null
+++ b/calendar-list-view-demo/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in E:\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/calendar-list-view-demo/app/src/androidTest/java/com/rogary/calendarlistviewdemo/ApplicationTest.java b/calendar-list-view-demo/app/src/androidTest/java/com/rogary/calendarlistviewdemo/ApplicationTest.java
new file mode 100644
index 0000000..d1bb99d
--- /dev/null
+++ b/calendar-list-view-demo/app/src/androidTest/java/com/rogary/calendarlistviewdemo/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.rogary.calendarlistviewdemo;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase< Application > {
+ public ApplicationTest () {
+ super ( Application.class );
+ }
+}
\ No newline at end of file
diff --git a/calendar-list-view-demo/app/src/main/AndroidManifest.xml b/calendar-list-view-demo/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..85bc78d
--- /dev/null
+++ b/calendar-list-view-demo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/calendar-list-view-demo/app/src/main/java/com/rogary/calendarlistviewdemo/CalandarDemoActivity.java b/calendar-list-view-demo/app/src/main/java/com/rogary/calendarlistviewdemo/CalandarDemoActivity.java
new file mode 100644
index 0000000..5d5a1d9
--- /dev/null
+++ b/calendar-list-view-demo/app/src/main/java/com/rogary/calendarlistviewdemo/CalandarDemoActivity.java
@@ -0,0 +1,52 @@
+package com.rogary.calendarlistviewdemo;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.andexert.calendarlistview.library.DatePickerController;
+import com.andexert.calendarlistview.library.DayPickerView;
+import com.andexert.calendarlistview.library.SimpleMonthAdapter;
+
+
+public class CalandarDemoActivity extends Activity implements DatePickerController {
+
+ private TextView selectedDaysText; //显示已选择的范围
+ private DayPickerView dayPickerView; //日期选择View
+ private TextView selectedText; //显示点击的日期
+
+ @Override
+ protected void onCreate ( Bundle savedInstanceState ) {
+ super.onCreate ( savedInstanceState );
+ setContentView ( R.layout.activity_calandar_demo );
+ initView ();
+ }
+
+
+ private void initView () {
+ selectedText = ( TextView ) findViewById ( R.id.activity_text );
+ selectedDaysText = ( TextView ) findViewById ( R.id.activity_selectd_text );
+ dayPickerView = ( DayPickerView ) findViewById ( R.id.activity_daypicker );
+ dayPickerView.setController ( this );
+ }
+
+ @Override
+ public int getMaxYear () {
+ return 2017;
+ }
+
+ @Override
+ public void onDayOfMonthSelected ( int year, int month, int day ) {
+ selectedText.setText ( getResources ().getString ( R.string.selected ) + year + getResources ().getString ( R.string.year ) + month + getResources ().getString ( R.string.month ) + day + getResources ().getString ( R.string.day ) );
+ selectedDaysText.setText("");
+ }
+
+ @Override
+ public void onDateRangeSelected ( SimpleMonthAdapter.SelectedDays< SimpleMonthAdapter.CalendarDay > selectedDays ) {
+ selectedDaysText.setText ( getResources ().getString ( R.string.selected ) + selectedDays.getFirst ().toString () + " --> " + selectedDays.getLast ().toString () );
+ }
+}
diff --git a/calendar-list-view-demo/app/src/main/res/drawable-hdpi/ic_launcher.png b/calendar-list-view-demo/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/calendar-list-view-demo/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/calendar-list-view-demo/app/src/main/res/drawable-mdpi/ic_launcher.png b/calendar-list-view-demo/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
Binary files /dev/null and b/calendar-list-view-demo/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/calendar-list-view-demo/app/src/main/res/drawable-xhdpi/ic_launcher.png b/calendar-list-view-demo/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
Binary files /dev/null and b/calendar-list-view-demo/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/calendar-list-view-demo/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/calendar-list-view-demo/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
Binary files /dev/null and b/calendar-list-view-demo/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/calendar-list-view-demo/app/src/main/res/layout/activity_calandar_demo.xml b/calendar-list-view-demo/app/src/main/res/layout/activity_calandar_demo.xml
new file mode 100644
index 0000000..b931783
--- /dev/null
+++ b/calendar-list-view-demo/app/src/main/res/layout/activity_calandar_demo.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
diff --git a/calendar-list-view-demo/app/src/main/res/menu/menu_calandar_demo.xml b/calendar-list-view-demo/app/src/main/res/menu/menu_calandar_demo.xml
new file mode 100644
index 0000000..37b2072
--- /dev/null
+++ b/calendar-list-view-demo/app/src/main/res/menu/menu_calandar_demo.xml
@@ -0,0 +1,8 @@
+
diff --git a/calendar-list-view-demo/app/src/main/res/values-v21/styles.xml b/calendar-list-view-demo/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..dba3c41
--- /dev/null
+++ b/calendar-list-view-demo/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/calendar-list-view-demo/app/src/main/res/values-w820dp/dimens.xml b/calendar-list-view-demo/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/calendar-list-view-demo/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/calendar-list-view-demo/app/src/main/res/values/dimens.xml b/calendar-list-view-demo/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/calendar-list-view-demo/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/calendar-list-view-demo/app/src/main/res/values/strings.xml b/calendar-list-view-demo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a708137
--- /dev/null
+++ b/calendar-list-view-demo/app/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+
+ CalendarListViewDemo
+ Hello world!
+ Settings
+
+
diff --git a/calendar-list-view-demo/app/src/main/res/values/styles.xml b/calendar-list-view-demo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..ff6c9d2
--- /dev/null
+++ b/calendar-list-view-demo/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/calendar-list-view-demo/build.gradle b/calendar-list-view-demo/build.gradle
new file mode 100644
index 0000000..6356aab
--- /dev/null
+++ b/calendar-list-view-demo/build.gradle
@@ -0,0 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.0.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/calendar-list-view-demo/calendar-list-view-demo.iml b/calendar-list-view-demo/calendar-list-view-demo.iml
new file mode 100644
index 0000000..2a02201
--- /dev/null
+++ b/calendar-list-view-demo/calendar-list-view-demo.iml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/calendar-list-view-demo/gradle.properties b/calendar-list-view-demo/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/calendar-list-view-demo/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/calendar-list-view-demo/gradle/wrapper/gradle-wrapper.jar b/calendar-list-view-demo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/calendar-list-view-demo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/calendar-list-view-demo/gradle/wrapper/gradle-wrapper.properties b/calendar-list-view-demo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/calendar-list-view-demo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/calendar-list-view-demo/gradlew b/calendar-list-view-demo/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/calendar-list-view-demo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/calendar-list-view-demo/gradlew.bat b/calendar-list-view-demo/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/calendar-list-view-demo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/calendar-list-view-demo/library/.gitignore b/calendar-list-view-demo/library/.gitignore
new file mode 100644
index 0000000..0979fa5
--- /dev/null
+++ b/calendar-list-view-demo/library/.gitignore
@@ -0,0 +1,39 @@
+#Android generated
+bin
+gen
+
+#Eclipse
+.project
+.classpath
+.settings
+
+#IntelliJ IDEA
+.idea
+*.iml
+*.ipr
+*.iws
+out
+
+#Gradle
+.gradle
+build
+
+#Maven
+target
+release.properties
+pom.xml.*
+project.properties
+
+#Ant
+build.xml
+local.properties
+gradle.properties
+proguard.cfg
+
+#OSX
+.DS_Store/
+
+# Crashlytics
+com_crashlytics_export_strings.xml
+crashlytics-build.properties
+crashlytics.properties
\ No newline at end of file
diff --git a/calendar-list-view-demo/library/LICENSE.txt b/calendar-list-view-demo/library/LICENSE.txt
new file mode 100644
index 0000000..3c9829c
--- /dev/null
+++ b/calendar-list-view-demo/library/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Robin Chutaux
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/calendar-list-view-demo/library/build.gradle b/calendar-list-view-demo/library/build.gradle
new file mode 100644
index 0000000..dd5b03c
--- /dev/null
+++ b/calendar-list-view-demo/library/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.1"
+
+ defaultConfig {
+ //applicationId "com.andexert.calendarlistview.library"
+ minSdkVersion 10
+ targetSdkVersion 21
+ versionCode 9
+ versionName "1.2.3"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:21.0.0'
+ compile 'com.android.support:recyclerview-v7:21.0.0'
+}
diff --git a/calendar-list-view-demo/library/proguard-rules.pro b/calendar-list-view-demo/library/proguard-rules.pro
new file mode 100644
index 0000000..48a6810
--- /dev/null
+++ b/calendar-list-view-demo/library/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:/Users/robin.chutaux/Documents/adt/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/calendar-list-view-demo/library/src/main/AndroidManifest.xml b/calendar-list-view-demo/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a801f9d
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/CalendarUtils.java b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/CalendarUtils.java
new file mode 100644
index 0000000..20dbe76
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/CalendarUtils.java
@@ -0,0 +1,52 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.andexert.calendarlistview.library;
+
+import java.util.Calendar;
+
+
+public class CalendarUtils
+{
+ public static int getDaysInMonth(int month, int year) {
+ switch (month) {
+ case Calendar.JANUARY:
+ case Calendar.MARCH:
+ case Calendar.MAY:
+ case Calendar.JULY:
+ case Calendar.AUGUST:
+ case Calendar.OCTOBER:
+ case Calendar.DECEMBER:
+ return 31;
+ case Calendar.APRIL:
+ case Calendar.JUNE:
+ case Calendar.SEPTEMBER:
+ case Calendar.NOVEMBER:
+ return 30;
+ case Calendar.FEBRUARY:
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) ? 28 : 29;
+ default:
+ throw new IllegalArgumentException("Invalid Month");
+ }
+ }
+}
diff --git a/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/DatePickerController.java b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/DatePickerController.java
new file mode 100644
index 0000000..3b9da03
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/DatePickerController.java
@@ -0,0 +1,33 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.andexert.calendarlistview.library;
+
+public interface DatePickerController {
+ public abstract int getMaxYear();
+
+ public abstract void onDayOfMonthSelected(int year, int month, int day);
+
+ public abstract void onDateRangeSelected(final SimpleMonthAdapter.SelectedDays selectedDays);
+
+}
\ No newline at end of file
diff --git a/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/DayPickerView.java b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/DayPickerView.java
new file mode 100644
index 0000000..1584aee
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/DayPickerView.java
@@ -0,0 +1,122 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.andexert.calendarlistview.library;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+
+public class DayPickerView extends RecyclerView
+{
+ protected Context mContext;
+ protected SimpleMonthAdapter mAdapter;
+ private DatePickerController mController;
+ protected int mCurrentScrollState = 0;
+ protected long mPreviousScrollPosition;
+ protected int mPreviousScrollState = 0;
+ private TypedArray typedArray;
+ private OnScrollListener onScrollListener;
+
+ public DayPickerView(Context context)
+ {
+ this(context, null);
+ }
+
+ public DayPickerView(Context context, AttributeSet attrs)
+ {
+ this(context, attrs, 0);
+ }
+
+ public DayPickerView(Context context, AttributeSet attrs, int defStyle)
+ {
+ super(context, attrs, defStyle);
+ if (!isInEditMode())
+ {
+ typedArray = context.obtainStyledAttributes(attrs, R.styleable.DayPickerView);
+ setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ init(context);
+ }
+ }
+
+ public void setController(DatePickerController mController)
+ {
+ this.mController = mController;
+ setUpAdapter();
+ setAdapter(mAdapter);
+ }
+
+
+ public void init(Context paramContext) {
+ setLayoutManager(new LinearLayoutManager(paramContext));
+ mContext = paramContext;
+ setUpListView();
+
+ onScrollListener = new OnScrollListener()
+ {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy)
+ {
+ super.onScrolled(recyclerView, dx, dy);
+ final SimpleMonthView child = (SimpleMonthView) recyclerView.getChildAt(0);
+ if (child == null) {
+ return;
+ }
+
+ mPreviousScrollPosition = dy;
+ mPreviousScrollState = mCurrentScrollState;
+ }
+ };
+ }
+
+
+ protected void setUpAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new SimpleMonthAdapter(getContext(), mController, typedArray);
+ }
+ mAdapter.notifyDataSetChanged();
+ }
+
+ protected void setUpListView() {
+ setVerticalScrollBarEnabled(false);
+ setOnScrollListener(onScrollListener);
+ setFadingEdgeLength(0);
+ }
+
+ public SimpleMonthAdapter.SelectedDays getSelectedDays()
+ {
+ return mAdapter.getSelectedDays();
+ }
+
+ protected DatePickerController getController()
+ {
+ return mController;
+ }
+
+ protected TypedArray getTypedArray()
+ {
+ return typedArray;
+ }
+}
\ No newline at end of file
diff --git a/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/SimpleMonthAdapter.java b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/SimpleMonthAdapter.java
new file mode 100644
index 0000000..20e1b0e
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/SimpleMonthAdapter.java
@@ -0,0 +1,291 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.andexert.calendarlistview.library;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.AbsListView;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+
+public class SimpleMonthAdapter extends RecyclerView.Adapter implements SimpleMonthView.OnDayClickListener {
+ protected static final int MONTHS_IN_YEAR = 12;
+ private final TypedArray typedArray;
+ private final Context mContext;
+ private final DatePickerController mController;
+ private final Calendar calendar;
+ private final SelectedDays selectedDays;
+ private final Integer firstMonth;
+ private final Integer lastMonth;
+
+ public SimpleMonthAdapter(Context context, DatePickerController datePickerController, TypedArray typedArray) {
+ this.typedArray = typedArray;
+ calendar = Calendar.getInstance();
+ firstMonth = typedArray.getInt(R.styleable.DayPickerView_firstMonth, calendar.get(Calendar.MONTH));
+ lastMonth = typedArray.getInt(R.styleable.DayPickerView_lastMonth, (calendar.get(Calendar.MONTH) - 1) % MONTHS_IN_YEAR);
+ selectedDays = new SelectedDays<>();
+ mContext = context;
+ mController = datePickerController;
+ init();
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i)
+ {
+ final SimpleMonthView simpleMonthView = new SimpleMonthView(mContext, typedArray);
+ return new ViewHolder(simpleMonthView, this);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder viewHolder, int position)
+ {
+ final SimpleMonthView v = viewHolder.simpleMonthView;
+ final HashMap drawingParams = new HashMap();
+ int month;
+ int year;
+
+ month = (firstMonth + (position % MONTHS_IN_YEAR)) % MONTHS_IN_YEAR;
+ year = position / MONTHS_IN_YEAR + calendar.get(Calendar.YEAR) + ((firstMonth + (position % MONTHS_IN_YEAR)) / MONTHS_IN_YEAR);
+
+ int selectedFirstDay = -1;
+ int selectedLastDay = -1;
+ int selectedFirstMonth = -1;
+ int selectedLastMonth = -1;
+ int selectedFirstYear = -1;
+ int selectedLastYear = -1;
+
+ if (selectedDays.getFirst() != null)
+ {
+ selectedFirstDay = selectedDays.getFirst().day;
+ selectedFirstMonth = selectedDays.getFirst().month;
+ selectedFirstYear = selectedDays.getFirst().year;
+ }
+
+ if (selectedDays.getLast() != null)
+ {
+ selectedLastDay = selectedDays.getLast().day;
+ selectedLastMonth = selectedDays.getLast().month;
+ selectedLastYear = selectedDays.getLast().year;
+ }
+
+ v.reuse();
+
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_BEGIN_YEAR, selectedFirstYear);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_LAST_YEAR, selectedLastYear);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_BEGIN_MONTH, selectedFirstMonth);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_LAST_MONTH, selectedLastMonth);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_BEGIN_DAY, selectedFirstDay);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_LAST_DAY, selectedLastDay);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_YEAR, year);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_MONTH, month);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_WEEK_START, calendar.getFirstDayOfWeek());
+ v.setMonthParams(drawingParams);
+ v.invalidate();
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getItemCount()
+ {
+ int itemCount = (((mController.getMaxYear() - calendar.get(Calendar.YEAR)) + 1) * MONTHS_IN_YEAR);
+
+ if (firstMonth != -1)
+ itemCount -= firstMonth;
+
+ if (lastMonth != -1)
+ itemCount -= (MONTHS_IN_YEAR - lastMonth) - 1;
+
+ return itemCount;
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder
+ {
+ final SimpleMonthView simpleMonthView;
+
+ public ViewHolder(View itemView, SimpleMonthView.OnDayClickListener onDayClickListener)
+ {
+ super(itemView);
+ simpleMonthView = (SimpleMonthView) itemView;
+ simpleMonthView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ simpleMonthView.setClickable(true);
+ simpleMonthView.setOnDayClickListener(onDayClickListener);
+ }
+ }
+
+ protected void init() {
+ if (typedArray.getBoolean(R.styleable.DayPickerView_currentDaySelected, false))
+ onDayTapped(new CalendarDay(System.currentTimeMillis()));
+ }
+
+ public void onDayClick(SimpleMonthView simpleMonthView, CalendarDay calendarDay) {
+ if (calendarDay != null) {
+ onDayTapped(calendarDay);
+ }
+ }
+
+ protected void onDayTapped(CalendarDay calendarDay) {
+ mController.onDayOfMonthSelected(calendarDay.year, calendarDay.month, calendarDay.day);
+ setSelectedDay(calendarDay);
+ }
+
+ public void setSelectedDay(CalendarDay calendarDay) {
+ if (selectedDays.getFirst() != null && selectedDays.getLast() == null)
+ {
+ selectedDays.setLast(calendarDay);
+
+ if (selectedDays.getFirst().month < calendarDay.month)
+ {
+ for (int i = 0; i < selectedDays.getFirst().month - calendarDay.month - 1; ++i)
+ mController.onDayOfMonthSelected(selectedDays.getFirst().year, selectedDays.getFirst().month + i, selectedDays.getFirst().day);
+ }
+
+ mController.onDateRangeSelected(selectedDays);
+ }
+ else if (selectedDays.getLast() != null)
+ {
+ selectedDays.setFirst(calendarDay);
+ selectedDays.setLast(null);
+ }
+ else
+ selectedDays.setFirst(calendarDay);
+
+ notifyDataSetChanged();
+ }
+
+ public static class CalendarDay implements Serializable
+ {
+ private static final long serialVersionUID = -5456695978688356202L;
+ private Calendar calendar;
+
+ int day;
+ int month;
+ int year;
+
+ public CalendarDay() {
+ setTime(System.currentTimeMillis());
+ }
+
+ public CalendarDay(int year, int month, int day) {
+ setDay(year, month, day);
+ }
+
+ public CalendarDay(long timeInMillis) {
+ setTime(timeInMillis);
+ }
+
+ public CalendarDay(Calendar calendar) {
+ year = calendar.get(Calendar.YEAR);
+ month = calendar.get(Calendar.MONTH);
+ day = calendar.get(Calendar.DAY_OF_MONTH);
+ }
+
+ private void setTime(long timeInMillis) {
+ if (calendar == null) {
+ calendar = Calendar.getInstance();
+ }
+ calendar.setTimeInMillis(timeInMillis);
+ month = this.calendar.get(Calendar.MONTH);
+ year = this.calendar.get(Calendar.YEAR);
+ day = this.calendar.get(Calendar.DAY_OF_MONTH);
+ }
+
+ public void set(CalendarDay calendarDay) {
+ year = calendarDay.year;
+ month = calendarDay.month;
+ day = calendarDay.day;
+ }
+
+ public void setDay(int year, int month, int day) {
+ this.year = year;
+ this.month = month;
+ this.day = day;
+ }
+
+ public Date getDate()
+ {
+ if (calendar == null) {
+ calendar = Calendar.getInstance();
+ }
+ calendar.set(year, month, day);
+ return calendar.getTime();
+ }
+
+ @Override
+ public String toString()
+ {
+ final StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("{ year: ");
+ stringBuilder.append(year);
+ stringBuilder.append(", month: ");
+ stringBuilder.append(month);
+ stringBuilder.append(", day: ");
+ stringBuilder.append(day);
+ stringBuilder.append(" }");
+
+ return stringBuilder.toString();
+ }
+ }
+
+ public SelectedDays getSelectedDays()
+ {
+ return selectedDays;
+ }
+
+ public static class SelectedDays implements Serializable
+ {
+ private static final long serialVersionUID = 3942549765282708376L;
+ private K first;
+ private K last;
+
+ public K getFirst()
+ {
+ return first;
+ }
+
+ public void setFirst(K first)
+ {
+ this.first = first;
+ }
+
+ public K getLast()
+ {
+ return last;
+ }
+
+ public void setLast(K last)
+ {
+ this.last = last;
+ }
+ }
+}
\ No newline at end of file
diff --git a/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/SimpleMonthView.java b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/SimpleMonthView.java
new file mode 100644
index 0000000..f61bdef
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/java/com/andexert/calendarlistview/library/SimpleMonthView.java
@@ -0,0 +1,448 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.andexert.calendarlistview.library;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.security.InvalidParameterException;
+import java.text.DateFormatSymbols;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Locale;
+
+class SimpleMonthView extends View
+{
+
+ public static final String VIEW_PARAMS_HEIGHT = "height";
+ public static final String VIEW_PARAMS_MONTH = "month";
+ public static final String VIEW_PARAMS_YEAR = "year";
+ public static final String VIEW_PARAMS_SELECTED_BEGIN_DAY = "selected_begin_day";
+ public static final String VIEW_PARAMS_SELECTED_LAST_DAY = "selected_last_day";
+ public static final String VIEW_PARAMS_SELECTED_BEGIN_MONTH = "selected_begin_month";
+ public static final String VIEW_PARAMS_SELECTED_LAST_MONTH = "selected_last_month";
+ public static final String VIEW_PARAMS_SELECTED_BEGIN_YEAR = "selected_begin_year";
+ public static final String VIEW_PARAMS_SELECTED_LAST_YEAR = "selected_last_year";
+ public static final String VIEW_PARAMS_WEEK_START = "week_start";
+
+ private static final int SELECTED_CIRCLE_ALPHA = 128;
+ protected static int DEFAULT_HEIGHT = 32;
+ protected static final int DEFAULT_NUM_ROWS = 6;
+ protected static int DAY_SELECTED_CIRCLE_SIZE;
+ protected static int DAY_SEPARATOR_WIDTH = 1;
+ protected static int MINI_DAY_NUMBER_TEXT_SIZE;
+ protected static int MIN_HEIGHT = 10;
+ protected static int MONTH_DAY_LABEL_TEXT_SIZE;
+ protected static int MONTH_HEADER_SIZE;
+ protected static int MONTH_LABEL_TEXT_SIZE;
+
+ protected int mPadding = 0;
+
+ private String mDayOfWeekTypeface;
+ private String mMonthTitleTypeface;
+
+ protected Paint mMonthDayLabelPaint;
+ protected Paint mMonthNumPaint;
+ protected Paint mMonthTitleBGPaint;
+ protected Paint mMonthTitlePaint;
+ protected Paint mSelectedCirclePaint;
+ protected int mCurrentDayTextColor;
+ protected int mMonthTextColor;
+ protected int mDayTextColor;
+ protected int mDayNumColor;
+ protected int mMonthTitleBGColor;
+ protected int mPreviousDayColor;
+ protected int mSelectedDaysColor;
+
+ private final StringBuilder mStringBuilder;
+
+ protected boolean mHasToday = false;
+ protected boolean mIsPrev = false;
+ protected int mSelectedBeginDay = -1;
+ protected int mSelectedLastDay = -1;
+ protected int mSelectedBeginMonth = -1;
+ protected int mSelectedLastMonth = -1;
+ protected int mSelectedBeginYear = -1;
+ protected int mSelectedLastYear = -1;
+ protected int mToday = -1;
+ protected int mWeekStart = 1;
+ protected int mNumDays = 7;
+ protected int mNumCells = mNumDays;
+ private int mDayOfWeekStart = 0;
+ protected int mMonth;
+ protected Boolean mDrawRect;
+ protected int mRowHeight = DEFAULT_HEIGHT;
+ protected int mWidth;
+ protected int mYear;
+ final Time today;
+
+ private final Calendar mCalendar;
+ private final Calendar mDayLabelCalendar;
+ private final Boolean isPrevDayEnabled;
+
+ private int mNumRows = DEFAULT_NUM_ROWS;
+
+ private DateFormatSymbols mDateFormatSymbols = new DateFormatSymbols();
+
+ private OnDayClickListener mOnDayClickListener;
+
+ public SimpleMonthView(Context context, TypedArray typedArray)
+ {
+ super(context);
+
+ Resources resources = context.getResources();
+ mDayLabelCalendar = Calendar.getInstance();
+ mCalendar = Calendar.getInstance();
+ today = new Time(Time.getCurrentTimezone());
+ today.setToNow();
+ mDayOfWeekTypeface = resources.getString(R.string.sans_serif);
+ mMonthTitleTypeface = resources.getString(R.string.sans_serif);
+ mCurrentDayTextColor = typedArray.getColor(R.styleable.DayPickerView_colorCurrentDay, resources.getColor(R.color.normal_day));
+ mMonthTextColor = typedArray.getColor(R.styleable.DayPickerView_colorMonthName, resources.getColor(R.color.normal_day));
+ mDayTextColor = typedArray.getColor(R.styleable.DayPickerView_colorDayName, resources.getColor(R.color.normal_day));
+ mDayNumColor = typedArray.getColor(R.styleable.DayPickerView_colorNormalDay, resources.getColor(R.color.normal_day));
+ mPreviousDayColor = typedArray.getColor(R.styleable.DayPickerView_colorPreviousDay, resources.getColor(R.color.normal_day));
+ mSelectedDaysColor = typedArray.getColor(R.styleable.DayPickerView_colorSelectedDayBackground, resources.getColor(R.color.selected_day_background));
+ mMonthTitleBGColor = typedArray.getColor(R.styleable.DayPickerView_colorSelectedDayText, resources.getColor(R.color.selected_day_text));
+
+ mDrawRect = typedArray.getBoolean(R.styleable.DayPickerView_drawRoundRect, false);
+
+ mStringBuilder = new StringBuilder(50);
+
+ MINI_DAY_NUMBER_TEXT_SIZE = typedArray.getDimensionPixelSize(R.styleable.DayPickerView_textSizeDay, resources.getDimensionPixelSize(R.dimen.text_size_day));
+ MONTH_LABEL_TEXT_SIZE = typedArray.getDimensionPixelSize(R.styleable.DayPickerView_textSizeMonth, resources.getDimensionPixelSize(R.dimen.text_size_month));
+ MONTH_DAY_LABEL_TEXT_SIZE = typedArray.getDimensionPixelSize(R.styleable.DayPickerView_textSizeDayName, resources.getDimensionPixelSize(R.dimen.text_size_day_name));
+ MONTH_HEADER_SIZE = typedArray.getDimensionPixelOffset(R.styleable.DayPickerView_headerMonthHeight, resources.getDimensionPixelOffset(R.dimen.header_month_height));
+ DAY_SELECTED_CIRCLE_SIZE = typedArray.getDimensionPixelSize(R.styleable.DayPickerView_selectedDayRadius, resources.getDimensionPixelOffset(R.dimen.selected_day_radius));
+
+ mRowHeight = ((typedArray.getDimensionPixelSize(R.styleable.DayPickerView_calendarHeight, resources.getDimensionPixelOffset(R.dimen.calendar_height)) - MONTH_HEADER_SIZE) / 6);
+
+ isPrevDayEnabled = typedArray.getBoolean(R.styleable.DayPickerView_enablePreviousDay, true);
+
+ initView();
+
+ }
+
+ private int calculateNumRows() {
+ int offset = findDayOffset();
+ int dividend = (offset + mNumCells) / mNumDays;
+ int remainder = (offset + mNumCells) % mNumDays;
+ return (dividend + (remainder > 0 ? 1 : 0));
+ }
+
+ private void drawMonthDayLabels(Canvas canvas) {
+ int y = MONTH_HEADER_SIZE - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
+ int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+
+ for (int i = 0; i < mNumDays; i++) {
+ int calendarDay = (i + mWeekStart) % mNumDays;
+ int x = (2 * i + 1) * dayWidthHalf + mPadding;
+ mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
+ canvas.drawText(mDateFormatSymbols.getShortWeekdays()[mDayLabelCalendar.get(Calendar.DAY_OF_WEEK)].toUpperCase(Locale.getDefault()), x, y, mMonthDayLabelPaint);
+ }
+ }
+
+ private void drawMonthTitle(Canvas canvas) {
+ int x = (mWidth + 2 * mPadding) / 2;
+ int y = (MONTH_HEADER_SIZE - MONTH_DAY_LABEL_TEXT_SIZE) / 2 + (MONTH_LABEL_TEXT_SIZE / 3);
+ StringBuilder stringBuilder = new StringBuilder(getMonthAndYearString().toLowerCase());
+ stringBuilder.setCharAt(0, Character.toUpperCase(stringBuilder.charAt(0)));
+ canvas.drawText(stringBuilder.toString(), x, y, mMonthTitlePaint);
+ }
+
+ private int findDayOffset() {
+ return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
+ - mWeekStart;
+ }
+
+ private String getMonthAndYearString() {
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_NO_MONTH_DAY;
+ mStringBuilder.setLength(0);
+ long millis = mCalendar.getTimeInMillis();
+ return DateUtils.formatDateRange(getContext(), millis, millis, flags);
+ }
+
+ private void onDayClick(SimpleMonthAdapter.CalendarDay calendarDay) {
+ if (mOnDayClickListener != null && (isPrevDayEnabled || !((calendarDay.month == today.month) && (calendarDay.year == today.year) && calendarDay.day < today.monthDay))) {
+ mOnDayClickListener.onDayClick(this, calendarDay);
+ }
+ }
+
+ private boolean sameDay(int monthDay, Time time) {
+ return (mYear == time.year) && (mMonth == time.month) && (monthDay == time.monthDay);
+ }
+
+ private boolean prevDay(int monthDay, Time time) {
+ return ((mYear < time.year)) || (mYear == time.year && mMonth < time.month) || ( mMonth == time.month && monthDay < time.monthDay);
+ }
+
+ protected void drawMonthNums(Canvas canvas) {
+ int y = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH + MONTH_HEADER_SIZE;
+ int paddingDay = (mWidth - 2 * mPadding) / (2 * mNumDays);
+ int dayOffset = findDayOffset();
+ int day = 1;
+
+ while (day <= mNumCells) {
+ int x = paddingDay * (1 + dayOffset * 2) + mPadding;
+ if ((mMonth == mSelectedBeginMonth && mSelectedBeginDay == day && mSelectedBeginYear == mYear) || (mMonth == mSelectedLastMonth && mSelectedLastDay == day && mSelectedLastYear == mYear)) {
+ if (mDrawRect)
+ {
+ RectF rectF = new RectF(x - DAY_SELECTED_CIRCLE_SIZE, (y - MINI_DAY_NUMBER_TEXT_SIZE / 3) - DAY_SELECTED_CIRCLE_SIZE, x + DAY_SELECTED_CIRCLE_SIZE, (y - MINI_DAY_NUMBER_TEXT_SIZE / 3) + DAY_SELECTED_CIRCLE_SIZE);
+ canvas.drawRoundRect(rectF, 10.0f, 10.0f,mSelectedCirclePaint);
+ }
+ else
+ canvas.drawCircle(x, y - MINI_DAY_NUMBER_TEXT_SIZE / 3, DAY_SELECTED_CIRCLE_SIZE, mSelectedCirclePaint);
+ }
+ if (mHasToday && (mToday == day)) {
+ mMonthNumPaint.setColor(mCurrentDayTextColor);
+ mMonthNumPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+ } else {
+ mMonthNumPaint.setColor(mDayNumColor);
+ mMonthNumPaint.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
+ }
+
+ if ((mMonth == mSelectedBeginMonth && mSelectedBeginDay == day && mSelectedBeginYear == mYear) || (mMonth == mSelectedLastMonth && mSelectedLastDay == day && mSelectedLastYear == mYear))
+ mMonthNumPaint.setColor(mMonthTitleBGColor);
+
+ if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear == mSelectedLastYear &&
+ mSelectedBeginMonth == mSelectedLastMonth &&
+ mSelectedBeginDay == mSelectedLastDay &&
+ day == mSelectedBeginDay &&
+ mMonth == mSelectedBeginMonth &&
+ mYear == mSelectedBeginYear))
+ mMonthNumPaint.setColor(mSelectedDaysColor);
+
+ if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear == mSelectedLastYear && mSelectedBeginYear == mYear) &&
+ (((mMonth == mSelectedBeginMonth && mSelectedLastMonth == mSelectedBeginMonth) && ((mSelectedBeginDay < mSelectedLastDay && day > mSelectedBeginDay && day < mSelectedLastDay) || (mSelectedBeginDay > mSelectedLastDay && day < mSelectedBeginDay && day > mSelectedLastDay))) ||
+ ((mSelectedBeginMonth < mSelectedLastMonth && mMonth == mSelectedBeginMonth && day > mSelectedBeginDay) || (mSelectedBeginMonth < mSelectedLastMonth && mMonth == mSelectedLastMonth && day < mSelectedLastDay)) ||
+ ((mSelectedBeginMonth > mSelectedLastMonth && mMonth == mSelectedBeginMonth && day < mSelectedBeginDay) || (mSelectedBeginMonth > mSelectedLastMonth && mMonth == mSelectedLastMonth && day > mSelectedLastDay))))
+ {
+ mMonthNumPaint.setColor(mSelectedDaysColor);
+ }
+
+ if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear != mSelectedLastYear && ((mSelectedBeginYear == mYear && mMonth == mSelectedBeginMonth) || (mSelectedLastYear == mYear && mMonth == mSelectedLastMonth)) &&
+ (((mSelectedBeginMonth < mSelectedLastMonth && mMonth == mSelectedBeginMonth && day < mSelectedBeginDay) || (mSelectedBeginMonth < mSelectedLastMonth && mMonth == mSelectedLastMonth && day > mSelectedLastDay)) ||
+ ((mSelectedBeginMonth > mSelectedLastMonth && mMonth == mSelectedBeginMonth && day > mSelectedBeginDay) || (mSelectedBeginMonth > mSelectedLastMonth && mMonth == mSelectedLastMonth && day < mSelectedLastDay)))))
+ {
+ mMonthNumPaint.setColor(mSelectedDaysColor);
+ }
+
+ if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear == mSelectedLastYear && mYear == mSelectedBeginYear) &&
+ ((mMonth > mSelectedBeginMonth && mMonth < mSelectedLastMonth && mSelectedBeginMonth < mSelectedLastMonth) ||
+ (mMonth < mSelectedBeginMonth && mMonth > mSelectedLastMonth && mSelectedBeginMonth > mSelectedLastMonth)))
+ {
+ mMonthNumPaint.setColor(mSelectedDaysColor);
+ }
+
+ if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear != mSelectedLastYear) &&
+ ((mSelectedBeginYear < mSelectedLastYear && ((mMonth > mSelectedBeginMonth && mYear == mSelectedBeginYear) || (mMonth < mSelectedLastMonth && mYear == mSelectedLastYear))) ||
+ (mSelectedBeginYear > mSelectedLastYear && ((mMonth < mSelectedBeginMonth && mYear == mSelectedBeginYear) || (mMonth > mSelectedLastMonth && mYear == mSelectedLastYear)))))
+ {
+ mMonthNumPaint.setColor(mSelectedDaysColor);
+ }
+
+ if (!isPrevDayEnabled && prevDay(day, today) && today.month == mMonth && today.year == mYear)
+ {
+ mMonthNumPaint.setColor(mPreviousDayColor);
+ mMonthNumPaint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
+ }
+
+ canvas.drawText(String.format("%d", day), x, y, mMonthNumPaint);
+
+ dayOffset++;
+ if (dayOffset == mNumDays) {
+ dayOffset = 0;
+ y += mRowHeight;
+ }
+ day++;
+ }
+ }
+
+ public SimpleMonthAdapter.CalendarDay getDayFromLocation(float x, float y) {
+ int padding = mPadding;
+ if ((x < padding) || (x > mWidth - mPadding)) {
+ return null;
+ }
+
+ int yDay = (int) (y - MONTH_HEADER_SIZE) / mRowHeight;
+ int day = 1 + ((int) ((x - padding) * mNumDays / (mWidth - padding - mPadding)) - findDayOffset()) + yDay * mNumDays;
+
+ if (mMonth > 11 || mMonth < 0 || CalendarUtils.getDaysInMonth(mMonth, mYear) < day || day < 1)
+ return null;
+
+ return new SimpleMonthAdapter.CalendarDay(mYear, mMonth, day);
+ }
+
+ protected void initView() {
+ mMonthTitlePaint = new Paint();
+ mMonthTitlePaint.setFakeBoldText(true);
+ mMonthTitlePaint.setAntiAlias(true);
+ mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
+ mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
+ mMonthTitlePaint.setColor(mMonthTextColor);
+ mMonthTitlePaint.setTextAlign(Align.CENTER);
+ mMonthTitlePaint.setStyle(Style.FILL);
+
+ mMonthTitleBGPaint = new Paint();
+ mMonthTitleBGPaint.setFakeBoldText(true);
+ mMonthTitleBGPaint.setAntiAlias(true);
+ mMonthTitleBGPaint.setColor(mMonthTitleBGColor);
+ mMonthTitleBGPaint.setTextAlign(Align.CENTER);
+ mMonthTitleBGPaint.setStyle(Style.FILL);
+
+ mSelectedCirclePaint = new Paint();
+ mSelectedCirclePaint.setFakeBoldText(true);
+ mSelectedCirclePaint.setAntiAlias(true);
+ mSelectedCirclePaint.setColor(mSelectedDaysColor);
+ mSelectedCirclePaint.setTextAlign(Align.CENTER);
+ mSelectedCirclePaint.setStyle(Style.FILL);
+ mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+
+ mMonthDayLabelPaint = new Paint();
+ mMonthDayLabelPaint.setAntiAlias(true);
+ mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
+ mMonthDayLabelPaint.setColor(mDayTextColor);
+ mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
+ mMonthDayLabelPaint.setStyle(Style.FILL);
+ mMonthDayLabelPaint.setTextAlign(Align.CENTER);
+ mMonthDayLabelPaint.setFakeBoldText(true);
+
+ mMonthNumPaint = new Paint();
+ mMonthNumPaint.setAntiAlias(true);
+ mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
+ mMonthNumPaint.setStyle(Style.FILL);
+ mMonthNumPaint.setTextAlign(Align.CENTER);
+ mMonthNumPaint.setFakeBoldText(false);
+ }
+
+ protected void onDraw(Canvas canvas) {
+ drawMonthTitle(canvas);
+ drawMonthDayLabels(canvas);
+ drawMonthNums(canvas);
+ }
+
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows + MONTH_HEADER_SIZE);
+ }
+
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ SimpleMonthAdapter.CalendarDay calendarDay = getDayFromLocation(event.getX(), event.getY());
+ if (calendarDay != null) {
+ onDayClick(calendarDay);
+ }
+ }
+ return true;
+ }
+
+ public void reuse() {
+ mNumRows = DEFAULT_NUM_ROWS;
+ requestLayout();
+ }
+
+ public void setMonthParams(HashMap params) {
+ if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
+ throw new InvalidParameterException("You must specify month and year for this view");
+ }
+ setTag(params);
+
+ if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
+ mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
+ if (mRowHeight < MIN_HEIGHT) {
+ mRowHeight = MIN_HEIGHT;
+ }
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_BEGIN_DAY)) {
+ mSelectedBeginDay = params.get(VIEW_PARAMS_SELECTED_BEGIN_DAY);
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_LAST_DAY)) {
+ mSelectedLastDay = params.get(VIEW_PARAMS_SELECTED_LAST_DAY);
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_BEGIN_MONTH)) {
+ mSelectedBeginMonth = params.get(VIEW_PARAMS_SELECTED_BEGIN_MONTH);
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_LAST_MONTH)) {
+ mSelectedLastMonth = params.get(VIEW_PARAMS_SELECTED_LAST_MONTH);
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_BEGIN_YEAR)) {
+ mSelectedBeginYear = params.get(VIEW_PARAMS_SELECTED_BEGIN_YEAR);
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_LAST_YEAR)) {
+ mSelectedLastYear = params.get(VIEW_PARAMS_SELECTED_LAST_YEAR);
+ }
+
+ mMonth = params.get(VIEW_PARAMS_MONTH);
+ mYear = params.get(VIEW_PARAMS_YEAR);
+
+ mHasToday = false;
+ mToday = -1;
+
+ mCalendar.set(Calendar.MONTH, mMonth);
+ mCalendar.set(Calendar.YEAR, mYear);
+ mCalendar.set(Calendar.DAY_OF_MONTH, 1);
+ mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
+
+ if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
+ mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
+ } else {
+ mWeekStart = mCalendar.getFirstDayOfWeek();
+ }
+
+ mNumCells = CalendarUtils.getDaysInMonth(mMonth, mYear);
+ for (int i = 0; i < mNumCells; i++) {
+ final int day = i + 1;
+ if (sameDay(day, today)) {
+ mHasToday = true;
+ mToday = day;
+ }
+
+ mIsPrev = prevDay(day, today);
+ }
+
+ mNumRows = calculateNumRows();
+ }
+
+ public void setOnDayClickListener(OnDayClickListener onDayClickListener) {
+ mOnDayClickListener = onDayClickListener;
+ }
+
+ public static abstract interface OnDayClickListener {
+ public abstract void onDayClick(SimpleMonthView simpleMonthView, SimpleMonthAdapter.CalendarDay calendarDay);
+ }
+}
\ No newline at end of file
diff --git a/calendar-list-view-demo/library/src/main/res/drawable-hdpi/ic_launcher.png b/calendar-list-view-demo/library/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/calendar-list-view-demo/library/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/calendar-list-view-demo/library/src/main/res/drawable-mdpi/ic_launcher.png b/calendar-list-view-demo/library/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
Binary files /dev/null and b/calendar-list-view-demo/library/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/calendar-list-view-demo/library/src/main/res/drawable-xhdpi/ic_launcher.png b/calendar-list-view-demo/library/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
Binary files /dev/null and b/calendar-list-view-demo/library/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/calendar-list-view-demo/library/src/main/res/drawable-xxhdpi/ic_launcher.png b/calendar-list-view-demo/library/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
Binary files /dev/null and b/calendar-list-view-demo/library/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/calendar-list-view-demo/library/src/main/res/values/attrs.xml b/calendar-list-view-demo/library/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..c7bda69
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/res/values/attrs.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/calendar-list-view-demo/library/src/main/res/values/colors.xml b/calendar-list-view-demo/library/src/main/res/values/colors.xml
new file mode 100644
index 0000000..9bd379d
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+
+ #ff999999
+ #E75F49
+ #fff2f2f2
+
\ No newline at end of file
diff --git a/calendar-list-view-demo/library/src/main/res/values/dimens.xml b/calendar-list-view-demo/library/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..e7bfa43
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/res/values/dimens.xml
@@ -0,0 +1,9 @@
+
+
+ 16sp
+ 16sp
+ 10sp
+ 50dip
+ 16dip
+ 270dip
+
\ No newline at end of file
diff --git a/calendar-list-view-demo/library/src/main/res/values/strings.xml b/calendar-list-view-demo/library/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7ee28e1
--- /dev/null
+++ b/calendar-list-view-demo/library/src/main/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+ CalendarListview
+
+
+ sans-serif
+
+ 您选择了:
+ 年
+ 月
+ 日
+
diff --git a/calendar-list-view-demo/settings.gradle b/calendar-list-view-demo/settings.gradle
new file mode 100644
index 0000000..9209dfb
--- /dev/null
+++ b/calendar-list-view-demo/settings.gradle
@@ -0,0 +1 @@
+include ':app' ,'library'
diff --git a/circular-reveal-demo/.gitignore b/circular-reveal-demo/.gitignore
new file mode 100644
index 0000000..afbdab3
--- /dev/null
+++ b/circular-reveal-demo/.gitignore
@@ -0,0 +1,6 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
diff --git a/circular-reveal-demo/README.md b/circular-reveal-demo/README.md
new file mode 100644
index 0000000..96cb2b4
--- /dev/null
+++ b/circular-reveal-demo/README.md
@@ -0,0 +1,115 @@
+CircularReveal
+==============
+
+Lollipop ViewAnimationUtils.createCircularReveal for everyone 2.3+
+
+
+
+Sample
+======
+ Sample & .aar file
+
+Note
+====
+
+depends from Jake Wharton's NineOldsAndroid, or use my modifed version (included auto cancel)
+
+Using
+======
+
+Use regular `RevealFrameLayout` & `RevealLinearLayout` don't worry, only target will be clipped :)
+
+```xml
+
+
+
+
+
+
+
+```
+
+```java
+
+ View myView = findView(R.id.awesome_card);
+
+ // get the center for the clipping circle
+ int cx = (myView.getLeft() + myView.getRight()) / 2;
+ int cy = (myView.getTop() + myView.getBottom()) / 2;
+
+ // get the final radius for the clipping circle
+ int finalRadius = Math.max(myView.getWidth(), myView.getHeight());
+
+ SupportAnimator animator =
+ ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
+ animator.setInterpolator(new AccelerateDecelerateInterpolator());
+ animator.setDuration(1500);
+ animator.start();
+
+```
+
+How to add dependency
+=====================
+
+This library is not released in Maven Central, but instead you can use [JitPack](https://www.jitpack.io/)
+
+add remote maven url
+
+```groovy
+ repositories {
+ maven {
+ url "https://jitpack.io"
+ }
+ }
+```
+
+then add a library dependency
+
+```groovy
+ dependencies {
+ compile 'com.github.ozodrukh:CircularReveal:(latest-release)@aar'
+ }
+```
+
+License
+--------
+
+ The MIT License (MIT)
+
+ Copyright (c) 2014 Abdullaev Ozodrukh
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+I
diff --git a/circular-reveal-demo/apk/Sample-1.0.apk b/circular-reveal-demo/apk/Sample-1.0.apk
new file mode 100644
index 0000000..3fa848f
Binary files /dev/null and b/circular-reveal-demo/apk/Sample-1.0.apk differ
diff --git a/circular-reveal-demo/build.gradle b/circular-reveal-demo/build.gradle
new file mode 100644
index 0000000..1bd4bd4
--- /dev/null
+++ b/circular-reveal-demo/build.gradle
@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.1.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ mavenCentral()
+ mavenLocal()
+ maven{
+ url 'http://oss.svmeng.com:8081/nexus/content/repositories/releases'
+ }
+ }
+}
diff --git a/circular-reveal-demo/circualreveal/.gitignore b/circular-reveal-demo/circualreveal/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/circular-reveal-demo/circualreveal/build.gradle b/circular-reveal-demo/circualreveal/build.gradle
new file mode 100644
index 0000000..b784cdf
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+
+
+ defaultConfig {
+ minSdkVersion 10
+ targetSdkVersion 21
+ }
+}
+
+dependencies {
+ compile 'com.nineoldandroids:library:2.4.0'
+}
diff --git a/circular-reveal-demo/circualreveal/circualreveal.iml b/circular-reveal-demo/circualreveal/circualreveal.iml
new file mode 100644
index 0000000..291b886
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/circualreveal.iml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/circular-reveal-demo/circualreveal/gradle.properties b/circular-reveal-demo/circualreveal/gradle.properties
new file mode 100644
index 0000000..f935e6f
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/gradle.properties
@@ -0,0 +1,3 @@
+POM_NAME=com.ozodrukh
+POM_ARTIFACT_ID=circularreveal
+POM_PACKAGING=aar
\ No newline at end of file
diff --git a/circular-reveal-demo/circualreveal/gradle_mvn_push.gradle b/circular-reveal-demo/circualreveal/gradle_mvn_push.gradle
new file mode 100644
index 0000000..7b2d95c
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/gradle_mvn_push.gradle
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 Chris Banes
+ *
+ * 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.
+ */
+
+apply plugin: 'maven'
+apply plugin: 'signing'
+
+def isReleaseBuild() {
+ return VERSION_NAME.contains("SNAPSHOT") == false
+}
+
+def getReleaseRepositoryUrl() {
+ return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
+ : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+}
+
+def getSnapshotRepositoryUrl() {
+ return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
+ : "https://oss.sonatype.org/content/repositories/snapshots/"
+}
+
+def getRepositoryUsername() {
+ return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
+}
+
+def getRepositoryPassword() {
+ return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
+}
+
+afterEvaluate { project ->
+ uploadArchives {
+ repositories {
+ mavenDeployer {
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+ pom.groupId = GROUP
+ pom.artifactId = POM_ARTIFACT_ID
+ pom.version = VERSION_NAME
+
+ repository(url: getReleaseRepositoryUrl()) {
+ authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
+ }
+ snapshotRepository(url: getSnapshotRepositoryUrl()) {
+ authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
+ }
+
+ pom.project {
+ name POM_NAME
+ packaging POM_PACKAGING
+ description POM_DESCRIPTION
+ url POM_URL
+
+ scm {
+ url POM_SCM_URL
+ connection POM_SCM_CONNECTION
+ developerConnection POM_SCM_DEV_CONNECTION
+ }
+
+ licenses {
+ license {
+ name POM_LICENCE_NAME
+ url POM_LICENCE_URL
+ distribution POM_LICENCE_DIST
+ }
+ }
+
+ developers {
+ developer {
+ id POM_DEVELOPER_ID
+ name POM_DEVELOPER_NAME
+ }
+ }
+ }
+ }
+ }
+ }
+
+ signing {
+ required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
+ sign configurations.archives
+ }
+
+ task androidJavadocs(type: Javadoc) {
+ source = android.sourceSets.main.java.srcDirs
+ classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+ }
+
+ task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
+ classifier = 'javadoc'
+ from androidJavadocs.destinationDir
+ }
+
+ task androidSourcesJar(type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.sourceFiles
+ }
+
+ artifacts {
+ archives androidSourcesJar
+ archives androidJavadocsJar
+ }
+}
\ No newline at end of file
diff --git a/circular-reveal-demo/circualreveal/proguard-rules.pro b/circular-reveal-demo/circualreveal/proguard-rules.pro
new file mode 100644
index 0000000..db6983a
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/Ozodrukh/Documents/Android/android-sdk-macosx/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/circular-reveal-demo/circualreveal/src/main/AndroidManifest.xml b/circular-reveal-demo/circualreveal/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..326cb30
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/circular-reveal-demo/circualreveal/src/main/java/io/codetail/animation/RevealAnimator.java b/circular-reveal-demo/circualreveal/src/main/java/io/codetail/animation/RevealAnimator.java
new file mode 100644
index 0000000..a5e1783
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/src/main/java/io/codetail/animation/RevealAnimator.java
@@ -0,0 +1,136 @@
+package io.codetail.animation;
+
+import android.annotation.TargetApi;
+import android.graphics.Rect;
+import android.os.Build;
+import android.view.View;
+
+import com.nineoldandroids.animation.Animator;
+
+import java.lang.ref.WeakReference;
+
+import static io.codetail.animation.ViewAnimationUtils.SimpleAnimationListener;
+
+/**
+ * @hide
+ */
+public interface RevealAnimator{
+
+ public void setClipOutlines(boolean clip);
+
+ public void setCenter(float cx, float cy);
+
+ public void setTarget(View target);
+
+ public void setRevealRadius(float value);
+
+ public float getRevealRadius();
+
+ public void invalidate(Rect bounds);
+
+ static class RevealFinishedGingerbread extends SimpleAnimationListener {
+ WeakReference mReference;
+ volatile Rect mInvalidateBounds;
+
+ RevealFinishedGingerbread(RevealAnimator target, Rect bounds) {
+ mReference = new WeakReference<>(target);
+ mInvalidateBounds = bounds;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+
+ RevealAnimator target = mReference.get();
+
+ if(target == null){
+ return;
+ }
+
+ target.setClipOutlines(false);
+ target.setCenter(0, 0);
+ target.setTarget(null);
+ target.invalidate(mInvalidateBounds);
+ }
+ }
+
+ static class RevealFinishedIceCreamSandwich extends SimpleAnimationListener {
+ WeakReference mReference;
+ volatile Rect mInvalidateBounds;
+
+ int mLayerType;
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ RevealFinishedIceCreamSandwich(RevealAnimator target, Rect bounds) {
+ mReference = new WeakReference<>(target);
+ mInvalidateBounds = bounds;
+
+ mLayerType = ((View) target).getLayerType();
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ ((View) mReference.get()).setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ ((View) mReference.get()).setLayerType(mLayerType, null);
+
+ RevealAnimator target = mReference.get();
+
+ if(target == null){
+ return;
+ }
+
+ target.setClipOutlines(false);
+ target.setCenter(0, 0);
+ target.setTarget(null);
+ target.invalidate(mInvalidateBounds);
+ }
+ }
+
+ static class RevealFinishedJellyBeanMr1 extends SimpleAnimationListener {
+ WeakReference mReference;
+ volatile Rect mInvalidateBounds;
+
+ int mLayerType;
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ RevealFinishedJellyBeanMr1(RevealAnimator target, Rect bounds) {
+ mReference = new WeakReference<>(target);
+ mInvalidateBounds = bounds;
+
+ mLayerType = ((View) target).getLayerType();
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ ((View) mReference.get()).setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ ((View) mReference.get()).setLayerType(mLayerType, null);
+
+ RevealAnimator target = mReference.get();
+
+ if(target == null){
+ return;
+ }
+
+ target.setClipOutlines(false);
+ target.setCenter(0, 0);
+ target.setTarget(null);
+ target.invalidate(mInvalidateBounds);
+ }
+ }
+}
\ No newline at end of file
diff --git a/circular-reveal-demo/circualreveal/src/main/java/io/codetail/animation/SupportAnimator.java b/circular-reveal-demo/circualreveal/src/main/java/io/codetail/animation/SupportAnimator.java
new file mode 100644
index 0000000..a724fe5
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/src/main/java/io/codetail/animation/SupportAnimator.java
@@ -0,0 +1,101 @@
+package io.codetail.animation;
+
+import android.view.animation.Interpolator;
+
+public abstract class SupportAnimator {
+
+ /**
+ * @return true if using native android animation framework, otherwise is
+ * nineoldandroids
+ */
+ public abstract boolean isNativeAnimator();
+
+ /**
+ * @return depends from {@link android.os.Build.VERSION} if sdk version
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and greater will return
+ * {@link android.animation.Animator} otherwise {@link com.nineoldandroids.animation.Animator}
+ */
+ public abstract Object get();
+
+ /**
+ * Starts this animation. If the animation has a nonzero startDelay, the animation will start
+ * running after that delay elapses. A non-delayed animation will have its initial
+ * value(s) set immediately, followed by calls to
+ * {@link android.animation.Animator.AnimatorListener#onAnimationStart(android.animation.Animator)}
+ * for any listeners of this animator.
+ *
+ *
The animation started by calling this method will be run on the thread that called
+ * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+ * this is not the case). Also, if the animation will animate
+ * properties of objects in the view hierarchy, then the calling thread should be the UI
+ * thread for that view hierarchy.
+ *
+ */
+ public abstract void start();
+
+ /**
+ * Sets the duration of the animation.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ */
+ public abstract void setDuration(int duration);
+
+ /**
+ * The time interpolator used in calculating the elapsed fraction of the
+ * animation. The interpolator determines whether the animation runs with
+ * linear or non-linear motion, such as acceleration and deceleration. The
+ * default value is {@link android.view.animation.AccelerateDecelerateInterpolator}.
+ *
+ * @param value the interpolator to be used by this animation
+ */
+ public abstract void setInterpolator(Interpolator value);
+
+
+ /**
+ * Adds a listener to the set of listeners that are sent events through the life of an
+ * animation, such as start, repeat, and end.
+ *
+ * @param listener the listener to be added to the current set of listeners for this animation.
+ */
+ public abstract void addListener(AnimatorListener listener);
+
+
+ /**
+ * Returns whether this Animator is currently running (having been started and gone past any
+ * initial startDelay period and not yet ended).
+ *
+ * @return Whether the Animator is running.
+ */
+ public abstract boolean isRunning();
+
+
+ /**
+ *
An animation listener receives notifications from an animation.
+ * Notifications indicate animation related events, such as the end or the
+ * repetition of the animation.
+ * Any shadow cast by the View will respect the circular clip from this animator.
+ *
+ * Only a single non-rectangular clip can be applied on a View at any time.
+ * Views clipped by a circular reveal animation take priority over
+ * {@link android.view.View#setClipToOutline(boolean) View Outline clipping}.
+ *
+ * Note that the animation returned here is a one-shot animation. It cannot
+ * be re-used, and once started it cannot be paused or resumed.
+ *
+ * @param view The View will be clipped to the animating circle.
+ * @param centerX The x coordinate of the center of the animating circle.
+ * @param centerY The y coordinate of the center of the animating circle.
+ * @param startRadius The starting radius of the animating circle.
+ * @param endRadius The ending radius of the animating circle.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static SupportAnimator createCircularReveal(View view,
+ int centerX, int centerY,
+ float startRadius, float endRadius) {
+
+ if(LOLLIPOP_PLUS){
+ return new SupportAnimatorLollipop(android.view.ViewAnimationUtils
+ .createCircularReveal(view, centerX, centerY, startRadius, endRadius));
+ }
+
+ if(!(view.getParent() instanceof RevealAnimator)){
+ throw new IllegalArgumentException("View must be inside RevealFrameLayout or RevealLinearLayout.");
+ }
+
+ RevealAnimator revealLayout = (RevealAnimator) view.getParent();
+ revealLayout.setTarget(view);
+ revealLayout.setCenter(centerX, centerY);
+
+ Rect bounds = new Rect();
+ view.getHitRect(bounds);
+
+ ObjectAnimator reveal = ObjectAnimator.ofFloat(revealLayout, "revealRadius", startRadius, endRadius);
+ reveal.addListener(getRevealFinishListener(revealLayout, bounds));
+
+ return new SupportAnimatorPreL(reveal);
+ }
+
+
+ static Animator.AnimatorListener getRevealFinishListener(RevealAnimator target, Rect bounds){
+ if(SDK_INT >= 17){
+ return new RevealAnimator.RevealFinishedJellyBeanMr1(target, bounds);
+ }else if(SDK_INT >= 14){
+ return new RevealAnimator.RevealFinishedIceCreamSandwich(target, bounds);
+ }else {
+ return new RevealAnimator.RevealFinishedGingerbread(target, bounds);
+ }
+ }
+
+
+ /**
+ * Lifting view
+ *
+ * @param view The animation target
+ * @param baseRotation initial Rotation X in 3D space
+ * @param fromY initial Y position of view
+ * @param duration aniamtion duration
+ * @param startDelay start delay before animation begin
+ */
+ public static void liftingFromBottom(View view, float baseRotation, float fromY, int duration, int startDelay){
+ ViewHelper.setRotationX(view, baseRotation);
+ ViewHelper.setTranslationY(view, fromY);
+
+ ViewPropertyAnimator
+ .animate(view)
+ .setInterpolator(new AccelerateDecelerateInterpolator())
+ .setDuration(duration)
+ .setStartDelay(startDelay)
+ .rotationX(0)
+ .translationY(0)
+ .start();
+
+ }
+
+ /**
+ * Lifting view
+ *
+ * @param view The animation target
+ * @param baseRotation initial Rotation X in 3D space
+ * @param duration aniamtion duration
+ * @param startDelay start delay before animation begin
+ */
+ public static void liftingFromBottom(View view, float baseRotation, int duration, int startDelay){
+ ViewHelper.setRotationX(view, baseRotation);
+ ViewHelper.setTranslationY(view, view.getHeight() / 3);
+
+ ViewPropertyAnimator
+ .animate(view)
+ .setInterpolator(new AccelerateDecelerateInterpolator())
+ .setDuration(duration)
+ .setStartDelay(startDelay)
+ .rotationX(0)
+ .translationY(0)
+ .start();
+
+ }
+
+ /**
+ * Lifting view
+ *
+ * @param view The animation target
+ * @param baseRotation initial Rotation X in 3D space
+ * @param duration aniamtion duration
+ */
+ public static void liftingFromBottom(View view, float baseRotation, int duration){
+ ViewHelper.setRotationX(view, baseRotation);
+ ViewHelper.setTranslationY(view, view.getHeight() / 3);
+
+ ViewPropertyAnimator
+ .animate(view)
+ .setInterpolator(new AccelerateDecelerateInterpolator())
+ .setDuration(duration)
+ .rotationX(0)
+ .translationY(0)
+ .start();
+
+ }
+
+ public static class SimpleAnimationListener implements Animator.AnimatorListener{
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ }
+
+}
diff --git a/circular-reveal-demo/circualreveal/src/main/java/io/codetail/widget/RevealFrameLayout.java b/circular-reveal-demo/circualreveal/src/main/java/io/codetail/widget/RevealFrameLayout.java
new file mode 100644
index 0000000..84cd7a4
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/src/main/java/io/codetail/widget/RevealFrameLayout.java
@@ -0,0 +1,109 @@
+package io.codetail.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import io.codetail.animation.RevealAnimator;
+
+public class RevealFrameLayout extends FrameLayout implements RevealAnimator{
+
+ Path mRevealPath;
+
+ boolean mClipOutlines;
+
+ float mCenterX;
+ float mCenterY;
+ float mRadius;
+
+ View mTarget;
+
+ public RevealFrameLayout(Context context) {
+ this(context, null);
+ }
+
+ public RevealFrameLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RevealFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mRevealPath = new Path();
+ }
+
+ /**
+ * Animation target
+ *
+ * @hide
+ */
+ @Override
+ public void setTarget(View view){
+ mTarget = view;
+ }
+
+ /**
+ * Epicenter of animation circle reveal
+ *
+ * @hide
+ */
+ @Override
+ public void setCenter(float centerX, float centerY){
+ mCenterX = centerX;
+ mCenterY = centerY;
+ }
+
+ /**
+ * Flag that animation is enabled
+ *
+ * @hide
+ */
+ @Override
+ public void setClipOutlines(boolean clip){
+ mClipOutlines = clip;
+ }
+
+ /**
+ * Circle radius size
+ *
+ * @hide
+ */
+ @Override
+ public void setRevealRadius(float radius){
+ mRadius = radius;
+ invalidate();
+ }
+
+ /**
+ * Circle radius size
+ *
+ * @hide
+ */
+ @Override
+ public float getRevealRadius(){
+ return mRadius;
+ }
+
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if(!mClipOutlines && child != mTarget)
+ return super.drawChild(canvas, child, drawingTime);
+
+ final int state = canvas.save();
+
+ mRevealPath.reset();
+ mRevealPath.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW);
+
+ canvas.clipPath(mRevealPath);
+
+ boolean isInvalided = super.drawChild(canvas, child, drawingTime);
+
+ canvas.restoreToCount(state);
+
+ return isInvalided;
+ }
+
+}
diff --git a/circular-reveal-demo/circualreveal/src/main/java/io/codetail/widget/RevealLinearLayout.java b/circular-reveal-demo/circualreveal/src/main/java/io/codetail/widget/RevealLinearLayout.java
new file mode 100644
index 0000000..594ff8f
--- /dev/null
+++ b/circular-reveal-demo/circualreveal/src/main/java/io/codetail/widget/RevealLinearLayout.java
@@ -0,0 +1,98 @@
+package io.codetail.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import io.codetail.animation.RevealAnimator;
+
+public class RevealLinearLayout extends LinearLayout implements RevealAnimator{
+
+ Path mRevealPath;
+
+ boolean mClipOutlines;
+
+ float mCenterX;
+ float mCenterY;
+ float mRadius;
+
+ View mTarget;
+
+ public RevealLinearLayout(Context context) {
+ this(context, null);
+ }
+
+ public RevealLinearLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RevealLinearLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs);
+ mRevealPath = new Path();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setTarget(View view){
+ mTarget = view;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setCenter(float centerX, float centerY){
+ mCenterX = centerX;
+ mCenterY = centerY;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setClipOutlines(boolean clip){
+ mClipOutlines = clip;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setRevealRadius(float radius){
+ mRadius = radius;
+ invalidate();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public float getRevealRadius(){
+ return mRadius;
+ }
+
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (!mClipOutlines && child != mTarget)
+ return super.drawChild(canvas, child, drawingTime);
+
+ final int state = canvas.save();
+
+ mRevealPath.reset();
+ mRevealPath.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW);
+
+ canvas.clipPath(mRevealPath);
+
+ boolean isInvalided = super.drawChild(canvas, child, drawingTime);
+
+ canvas.restoreToCount(state);
+
+ return isInvalided;
+ }
+}
diff --git a/circular-reveal-demo/circular-reveal-demo.iml b/circular-reveal-demo/circular-reveal-demo.iml
new file mode 100644
index 0000000..0bb6048
--- /dev/null
+++ b/circular-reveal-demo/circular-reveal-demo.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/circular-reveal-demo/gradle.properties b/circular-reveal-demo/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/circular-reveal-demo/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/circular-reveal-demo/gradle/wrapper/gradle-wrapper.jar b/circular-reveal-demo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/circular-reveal-demo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/circular-reveal-demo/gradle/wrapper/gradle-wrapper.properties b/circular-reveal-demo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/circular-reveal-demo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/circular-reveal-demo/gradlew b/circular-reveal-demo/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/circular-reveal-demo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/circular-reveal-demo/gradlew.bat b/circular-reveal-demo/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/circular-reveal-demo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/circular-reveal-demo/sample/.gitignore b/circular-reveal-demo/sample/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/circular-reveal-demo/sample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/circular-reveal-demo/sample/Sample.iml b/circular-reveal-demo/sample/Sample.iml
new file mode 100644
index 0000000..ca0c4ce
--- /dev/null
+++ b/circular-reveal-demo/sample/Sample.iml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/circular-reveal-demo/sample/build.gradle b/circular-reveal-demo/sample/build.gradle
new file mode 100644
index 0000000..48aaceb
--- /dev/null
+++ b/circular-reveal-demo/sample/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ applicationId "io.codetail.circualrevealsample"
+ minSdkVersion 10
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ lintOptions{
+ abortOnError false
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile project(':circualreveal')
+
+ compile 'com.android.support:appcompat-v7:21.0.3'
+ compile 'com.android.support:cardview-v7:21.0.0'
+ compile 'com.android.support:recyclerview-v7:21.0.0'
+
+ compile 'com.jakewharton:butterknife:6.0.0'
+ compile 'com.toaker.common:tlog:1.0.0@aar'
+}
diff --git a/circular-reveal-demo/sample/proguard-rules.pro b/circular-reveal-demo/sample/proguard-rules.pro
new file mode 100644
index 0000000..388e555
--- /dev/null
+++ b/circular-reveal-demo/sample/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in E:\android-studio-1.0\SDK/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/circular-reveal-demo/sample/src/androidTest/java/com/toaker/circularreveal/sample/ApplicationTest.java b/circular-reveal-demo/sample/src/androidTest/java/com/toaker/circularreveal/sample/ApplicationTest.java
new file mode 100644
index 0000000..1f0acc4
--- /dev/null
+++ b/circular-reveal-demo/sample/src/androidTest/java/com/toaker/circularreveal/sample/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.toaker.circularreveal.sample;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/circular-reveal-demo/sample/src/main/AndroidManifest.xml b/circular-reveal-demo/sample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1f98bad
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/MainActivity.java b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/MainActivity.java
new file mode 100644
index 0000000..6f0170c
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/MainActivity.java
@@ -0,0 +1,323 @@
+package com.toaker.circularreveal.sample;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.nineoldandroids.animation.ObjectAnimator;
+import com.nineoldandroids.view.ViewHelper;
+import com.toaker.circularreveal.sample.widget.FloatingActionButton;
+import com.toaker.circularreveal.sample.widget.ViewUtils;
+import com.toaker.common.tlog.TLog;
+
+import java.lang.ref.WeakReference;
+
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+import io.codetail.animation.SupportAnimator;
+import io.codetail.animation.ViewAnimationUtils;
+import io.codetail.widget.RevealFrameLayout;
+
+public class MainActivity extends ActionBarActivity{
+
+ public static final boolean DEBUG = true;
+
+ private static int VERSION = 1;
+
+ private static final String LOG_TAG = "circularreveal" + ++VERSION;
+
+ private static final int[] VIEW_ICONS = new int[]{R.drawable.icn_1,R.drawable.icn_2,
+ R.drawable.icn_3,R.drawable.icn_4,
+ R.drawable.icn_5,R.drawable.icn_6,
+ R.drawable.icn_7,};
+ @InjectView(R.id.toolbar)
+ Toolbar mToolbar;
+
+ @InjectView(R.id.floatingActionButton)
+ FloatingActionButton mFloatingButton;
+
+ @InjectView(R.id.cardsGroup)
+ RecyclerView mCardsGroup;
+
+ LinearLayoutManager mLayoutManager;
+ RecycleAdapter mCardsAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ ButterKnife.inject(this);
+ ButterKnife.setDebug(true);
+
+ setSupportActionBar(mToolbar);
+
+ mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
+ mCardsAdapter = new RecycleAdapter();
+ mCardsAdapter.setHasStableIds(true);
+
+ mCardsGroup.setHasFixedSize(true);
+ mCardsGroup.setItemViewCacheSize(3);
+ mCardsGroup.setClipToPadding(false);
+ mCardsGroup.setAdapter(mCardsAdapter);
+ mCardsGroup.setLayoutManager(mLayoutManager);
+
+ mToolbar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ ViewUtils.removeGlobalListeners(mToolbar, this);
+
+ final int outOfScreenY = ((ViewGroup.MarginLayoutParams) mFloatingButton.getLayoutParams())
+ .bottomMargin + mFloatingButton.getHeight();
+
+ ViewAnimationUtils.liftingFromBottom(mFloatingButton, 0, outOfScreenY, 500, 0);
+
+ if(DEBUG){
+ TLog.i(LOG_TAG,"onGlobalLayout");
+ }
+ }
+ });
+
+ mFloatingButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ final View myView = ((RevealFrameLayout) mCardsGroup.getChildAt(0)).getChildAt(0);
+
+ // get the center for the clipping circle
+ int cx = (myView.getLeft() + myView.getRight()) / 2;
+ int cy = (myView.getTop() + myView.getBottom()) / 2;
+// int cx = myView.getRight();
+// int cy = myView.getBottom();
+
+ // get the final radius for the clipping circle
+ float finalRadius = hypo(myView.getWidth(), myView.getHeight());
+
+ SupportAnimator animator =
+ ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
+ animator.addListener(new SupportAnimator.AnimatorListener() {
+ @Override
+ public void onAnimationStart() {
+ if(DEBUG){
+ TLog.i(LOG_TAG,"onAnimationStart");
+ }
+ }
+
+ @Override
+ public void onAnimationEnd() {
+ Toast.makeText(getApplicationContext(), "Done", Toast.LENGTH_LONG)
+ .show();
+ if(DEBUG){
+ TLog.i(LOG_TAG,"onAnimationEnd");
+ }
+ }
+
+ @Override
+ public void onAnimationCancel() {
+ if(DEBUG){
+ TLog.i(LOG_TAG,"onAnimationCancel");
+ }
+ }
+
+ @Override
+ public void onAnimationRepeat() {
+ if(DEBUG){
+ TLog.i(LOG_TAG,"onAnimationRepeat");
+ }
+ }
+ });
+ animator.setInterpolator(new AccelerateDecelerateInterpolator());
+ animator.setDuration(1500);
+ animator.start();
+ }
+ });
+
+ }
+
+ static float hypo(int a, int b){
+ return (float) Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mCardsGroup.setOnScrollListener(new HideExtraOnScroll(mToolbar));
+ if(DEBUG){
+ TLog.i(LOG_TAG,"Set OnScrollListener ");
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ // Prevent memory leaks, fuck yeah!
+ mCardsGroup.setOnScrollListener(null);
+ }
+
+ public static class RecycleAdapter extends RecyclerView.Adapter{
+
+ @Override
+ public CardHolder onCreateViewHolder(ViewGroup group, int i) {
+ LayoutInflater factory = LayoutInflater.from(group.getContext());
+ return new CardHolder(factory.inflate(R.layout.card_item, group, false));
+ }
+
+ @Override
+ public void onBindViewHolder(CardHolder cardHolder, int i) {
+ cardHolder.mIconView.setBackgroundResource(VIEW_ICONS[i]);
+ }
+
+ @Override
+ public int getItemCount() {
+ return VIEW_ICONS.length;
+ }
+ }
+
+ public static class CardHolder extends RecyclerView.ViewHolder{
+
+ @InjectView(R.id.card)
+ CardView mCard;
+
+ @InjectView(R.id.icon)
+ ImageView mIconView;
+
+ RevealFrameLayout mReveal;
+
+ public CardHolder(View itemView) {
+ super(itemView);
+ ButterKnife.inject(CardHolder.this, itemView);
+
+ mReveal = (RevealFrameLayout) mCard.getParent();
+ }
+ }
+
+
+ public static class HideExtraOnScrollHelper{
+ public final static int UNKNOWN = -1;
+ public final static int TOP = 0;
+ public final static int BOTTOM = 1;
+
+ int mDraggedAmount;
+ int mOldDirection;
+ int mDragDirection;
+
+ final int mMinFlingDistance;
+
+ public HideExtraOnScrollHelper(int minFlingDistance) {
+ mOldDirection =
+ mDragDirection =
+ mDraggedAmount = UNKNOWN;
+
+ mMinFlingDistance = minFlingDistance;
+ }
+
+ /**
+ * Checks need to hide extra objects on scroll or not
+ *
+ * @param dy scrolled distance y
+ * @return true if need to hide extra objects on screen
+ */
+ public boolean isObjectsShouldBeOutside(int dy){
+ boolean needHide = false;
+ mDragDirection = dy > 0 ? BOTTOM : TOP;
+
+ if(mDragDirection != mOldDirection){
+ mDraggedAmount = 0;
+ }
+
+ mDraggedAmount += dy;
+ boolean shouldBeOutside = false;
+
+ if(mDragDirection == TOP && Math.abs(mDraggedAmount) > mMinFlingDistance){
+ shouldBeOutside = false;
+ }else if(mDragDirection == BOTTOM && mDraggedAmount > mMinFlingDistance){
+ shouldBeOutside = true;
+ }
+
+ if(mOldDirection != mDragDirection){
+ mOldDirection = mDragDirection;
+ }
+
+ if(DEBUG){
+ TLog.i(LOG_TAG,"isObjectsShouldBeOutside dy:%s ",dy);
+ }
+
+ return shouldBeOutside;
+ }
+ }
+
+
+ public static class HideExtraOnScroll extends RecyclerView.OnScrollListener{
+
+ final static Interpolator ACCELERATE = new AccelerateInterpolator();
+ final static Interpolator DECELERATE = new DecelerateInterpolator();
+
+ WeakReference mTarget;
+ HideExtraOnScrollHelper mScrollHelper;
+
+ boolean isExtraObjectsOutside;
+
+ public HideExtraOnScroll(View target) {
+ int minimumFlingVelocity = ViewConfiguration.get(target.getContext())
+ .getScaledMinimumFlingVelocity();
+
+ mScrollHelper = new HideExtraOnScrollHelper(minimumFlingVelocity);
+ mTarget = new WeakReference<>(target);
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+
+ final View target = mTarget.get();
+
+ if(target == null) {
+ return;
+ }
+
+ boolean isObjectsShouldBeOutside = mScrollHelper.isObjectsShouldBeOutside(dy);
+
+ if(!isVisible(target) && !isObjectsShouldBeOutside){
+ show(target);
+ isExtraObjectsOutside = false;
+ }else if(isVisible(target) && isObjectsShouldBeOutside){
+ hide(target, -target.getHeight());
+ isExtraObjectsOutside = true;
+ }
+ }
+
+ public boolean isVisible(View target){
+ return !isExtraObjectsOutside;
+ }
+
+ public void hide(final View target, float distance){
+ ObjectAnimator animator = ObjectAnimator.ofFloat(target, "translationY",
+ ViewHelper.getTranslationY(target), distance);
+ animator.setInterpolator(DECELERATE);
+ animator.start();
+ }
+
+ public void show(final View target){
+ ObjectAnimator animator = ObjectAnimator.ofFloat(target, "translationY",
+ ViewHelper.getTranslationY(target), 0f);
+ animator.setInterpolator(ACCELERATE);
+ animator.start();
+ }
+
+ }
+
+}
diff --git a/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/DrawableHelper.java b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/DrawableHelper.java
new file mode 100644
index 0000000..8b66723
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/DrawableHelper.java
@@ -0,0 +1,24 @@
+package com.toaker.circularreveal.sample.widget;
+
+import android.annotation.TargetApi;
+import android.graphics.PorterDuff;
+import android.os.Build;
+
+public class DrawableHelper {
+ /*
+ * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode
+ * attribute's enum value.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
+ switch (value) {
+ case 3: return PorterDuff.Mode.SRC_OVER;
+ case 5: return PorterDuff.Mode.SRC_IN;
+ case 9: return PorterDuff.Mode.SRC_ATOP;
+ case 14: return PorterDuff.Mode.MULTIPLY;
+ case 15: return PorterDuff.Mode.SCREEN;
+ case 16: return PorterDuff.Mode.ADD;
+ default: return defaultMode;
+ }
+ }
+}
diff --git a/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/FloatingActionButton.java b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/FloatingActionButton.java
new file mode 100644
index 0000000..95f10ca
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/FloatingActionButton.java
@@ -0,0 +1,154 @@
+package com.toaker.circularreveal.sample.widget;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.toaker.circularreveal.sample.R;
+
+
+public class FloatingActionButton extends View{
+
+ Drawable mActionIcon;
+ final int mActionSize;
+
+ final int mInnerCircleOffset;
+ final Circle mCircle;
+ final Path mCirclePath;
+ final Paint mCirclePaint;
+
+ public FloatingActionButton(Context context) {
+ this(context, null);
+ }
+
+ public FloatingActionButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FloatingActionButton(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setFocusable(true);
+ setClickable(true);
+ ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, null);
+
+ Resources r = context.getResources();
+
+ mInnerCircleOffset = r.getDimensionPixelSize(R.dimen.fab_inner_circle_offset);
+ mActionSize = r.getDimensionPixelSize(R.dimen.fab_action_icon_size);
+
+ mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mCirclePaint.setStyle(Paint.Style.FILL);
+
+ // Resolving attribute styles
+ TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton);
+
+ mCirclePaint.setColor(array.getColor(R.styleable.FloatingActionButton_actionColor, Color.WHITE));
+ mActionIcon = array.getDrawable(R.styleable.FloatingActionButton_actionIcon);
+
+ if(mActionIcon != null){
+ mActionIcon.setBounds(0, 0, mActionSize, mActionSize);
+ }
+
+ array.recycle();
+
+ mCircle = new Circle();
+ mCirclePath = new Path();
+
+ ViewUtils.setBackground(this, r.getDrawable(R.drawable.floatingactionbutton_shadow_layer));
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mActionIcon || super.verifyDrawable(who);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ if(mActionIcon != null && mActionIcon.isStateful()){
+ mActionIcon.setState(getDrawableState());
+ }
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ if(mActionIcon != null){
+ mActionIcon.jumpToCurrentState();
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ final int size = Math.min(right - left, bottom - top) - (mInnerCircleOffset * 2);
+
+ mCircle.radius = size / 2;
+ mCircle.x = mCircle.y = mInnerCircleOffset + mCircle.radius;
+
+ mCirclePath.reset();
+ mCirclePath.addCircle(mCircle.x, mCircle.y, mCircle.radius, Path.Direction.CW);
+ }
+
+ public void setColor(int color){
+ mCirclePaint.setColor(color);
+
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ final int state = canvas.save();
+
+ canvas.clipPath(mCirclePath);
+ canvas.drawPath(mCirclePath, mCirclePaint);
+
+ if(mActionIcon != null){
+ final int beforeActionState = canvas.save();
+
+ canvas.translate( (mCircle.x - (mActionSize / 2)), (mCircle.y - (mActionSize / 2)));
+ mActionIcon.draw(canvas);
+
+ canvas.restoreToCount(beforeActionState);
+ }
+
+ canvas.restoreToCount(state);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if(mActionIcon != null){
+ mActionIcon.setVisible(ViewUtils.isVisible(this), false);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if(mActionIcon != null){
+ mActionIcon.setVisible(false, false);
+ }
+ }
+
+ private static class Circle{
+ float x, y, radius;
+ }
+}
diff --git a/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/FrameLayoutCompat.java b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/FrameLayoutCompat.java
new file mode 100644
index 0000000..5e1d2b6
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/FrameLayoutCompat.java
@@ -0,0 +1,724 @@
+package com.toaker.circularreveal.sample.widget;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+
+import com.toaker.circularreveal.sample.R;
+
+import java.util.ArrayList;
+
+
+
+// Port of FrameLayout for Android 2.3 Gingerbread
+public class FrameLayoutCompat extends ViewGroup{
+
+ static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
+
+ boolean mMeasureAllChildren = false;
+
+ private Drawable mForeground;
+ private ColorStateList mForegroundTintList = null;
+ private PorterDuff.Mode mForegroundTintMode = null;
+ private boolean mHasForegroundTint = false;
+ private boolean mHasForegroundTintMode = false;
+
+ private int mForegroundPaddingLeft = 0;
+
+ private int mForegroundPaddingTop = 0;
+
+ private int mForegroundPaddingRight = 0;
+
+ private int mForegroundPaddingBottom = 0;
+
+ private final Rect mSelfBounds = new Rect();
+ private final Rect mOverlayBounds = new Rect();
+
+ private int mForegroundGravity = Gravity.FILL;
+
+ protected boolean mForegroundInPadding = true;
+
+ boolean mForegroundBoundsChanged = false;
+
+ private final ArrayList mMatchParentChildren = new ArrayList(1);
+
+ public FrameLayoutCompat(Context context) {
+ super(context);
+ }
+
+ public FrameLayoutCompat(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FrameLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr, 0);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public FrameLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.FrameLayoutCompat, defStyleAttr, defStyleRes);
+
+ mForegroundGravity = a.getInt(
+ R.styleable.FrameLayoutCompat_foregroundGravity, mForegroundGravity);
+
+ final Drawable d = a.getDrawable(R.styleable.FrameLayoutCompat_foreground);
+ if (d != null) {
+ setForeground(d);
+ }
+
+ if (a.getBoolean(R.styleable.FrameLayoutCompat_measureAllChildren, false)) {
+ setMeasureAllChildren(true);
+ }
+
+ if (a.hasValue(R.styleable.FrameLayoutCompat_foregroundTintMode)) {
+ mForegroundTintMode = DrawableHelper.parseTintMode(a.getInt(
+ R.styleable.FrameLayoutCompat_foregroundTintMode, -1), mForegroundTintMode);
+ mHasForegroundTintMode = true;
+ }
+
+ if (a.hasValue(R.styleable.FrameLayoutCompat_foregroundTint)) {
+ mForegroundTintList = a.getColorStateList(R.styleable.FrameLayoutCompat_foregroundTint);
+ mHasForegroundTint = true;
+ }
+
+ mForegroundInPadding = a.getBoolean(R.styleable.FrameLayoutCompat_foregroundInsidePadding, true);
+
+ a.recycle();
+
+ applyForegroundTint();
+ }
+
+
+ /**
+ * Describes how the foreground is positioned.
+ *
+ * @return foreground gravity.
+ *
+ * @see #setForegroundGravity(int)
+ *
+ * @attr ref android.R.styleable#FrameLayout_foregroundGravity
+ */
+ public int getForegroundGravity() {
+ return mForegroundGravity;
+ }
+
+ /**
+ * Describes how the foreground is positioned. Defaults to START and TOP.
+ *
+ * @param foregroundGravity See {@link android.view.Gravity}
+ *
+ * @see #getForegroundGravity()
+ *
+ * @attr ref android.R.styleable#FrameLayout_foregroundGravity
+ */
+ public void setForegroundGravity(int foregroundGravity) {
+ if (mForegroundGravity != foregroundGravity) {
+ if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
+ foregroundGravity |= Gravity.START;
+ }
+
+ if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+ foregroundGravity |= Gravity.TOP;
+ }
+
+ mForegroundGravity = foregroundGravity;
+
+
+ if (mForegroundGravity == Gravity.FILL && mForeground != null) {
+ Rect padding = new Rect();
+ if (mForeground.getPadding(padding)) {
+ mForegroundPaddingLeft = padding.left;
+ mForegroundPaddingTop = padding.top;
+ mForegroundPaddingRight = padding.right;
+ mForegroundPaddingBottom = padding.bottom;
+ }
+ } else {
+ mForegroundPaddingLeft = 0;
+ mForegroundPaddingTop = 0;
+ mForegroundPaddingRight = 0;
+ mForegroundPaddingBottom = 0;
+ }
+
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ if (mForeground != null) {
+ mForeground.setVisible(visibility == VISIBLE, false);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || (who == mForeground);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ if (mForeground != null) mForeground.jumpToCurrentState();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ if (mForeground != null && mForeground.isStateful()) {
+ mForeground.setState(getDrawableState());
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public void drawableHotspotChanged(float x, float y) {
+ super.drawableHotspotChanged(x, y);
+
+ if (mForeground != null) {
+ mForeground.setHotspot(x, y);
+ }
+ }
+
+ /**
+ * Returns a set of layout parameters with a width of
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
+ * and a height of {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}.
+ */
+ @Override
+ protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
+ return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ }
+
+ /**
+ * Supply a Drawable that is to be rendered on top of all of the child
+ * views in the frame layout. Any padding in the Drawable will be taken
+ * into account by ensuring that the children are inset to be placed
+ * inside of the padding area.
+ *
+ * @param d The Drawable to be drawn on top of the children.
+ *
+ * @attr ref android.R.styleable#FrameLayout_foreground
+ */
+ public void setForeground(Drawable d) {
+ if (mForeground != d) {
+ if (mForeground != null) {
+ mForeground.setCallback(null);
+ unscheduleDrawable(mForeground);
+ }
+
+ mForeground = d;
+ mForegroundPaddingLeft = 0;
+ mForegroundPaddingTop = 0;
+ mForegroundPaddingRight = 0;
+ mForegroundPaddingBottom = 0;
+
+ if (d != null) {
+ setWillNotDraw(false);
+ d.setCallback(this);
+ if (d.isStateful()) {
+ d.setState(getDrawableState());
+ }
+ applyForegroundTint();
+ if (mForegroundGravity == Gravity.FILL) {
+ Rect padding = new Rect();
+ if (d.getPadding(padding)) {
+ mForegroundPaddingLeft = padding.left;
+ mForegroundPaddingTop = padding.top;
+ mForegroundPaddingRight = padding.right;
+ mForegroundPaddingBottom = padding.bottom;
+ }
+ }
+ } else {
+ setWillNotDraw(true);
+ }
+ requestLayout();
+ invalidate();
+ }
+ }
+
+ /**
+ * Returns the drawable used as the foreground of this FrameLayout. The
+ * foreground drawable, if non-null, is always drawn on top of the children.
+ *
+ * @return A Drawable or null if no foreground was set.
+ */
+ public Drawable getForeground() {
+ return mForeground;
+ }
+
+ /**
+ * Applies a tint to the foreground drawable. Does not modify the current
+ * tint mode, which is {@link android.graphics.PorterDuff.Mode#SRC_IN} by default.
+ *
+ * Subsequent calls to {@link #setForeground(android.graphics.drawable.Drawable)} will automatically
+ * mutate the drawable and apply the specified tint and tint mode using
+ * {@link android.graphics.drawable.Drawable#setTintList(android.content.res.ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#FrameLayout_foregroundTint
+ * @see #getForegroundTintList()
+ * @see android.graphics.drawable.Drawable#setTintList(android.content.res.ColorStateList)
+ */
+ public void setForegroundTintList(ColorStateList tint) {
+ mForegroundTintList = tint;
+ mHasForegroundTint = true;
+
+ applyForegroundTint();
+ }
+
+ /**
+ * @return the tint applied to the foreground drawable
+ * @attr ref android.R.styleable#FrameLayout_foregroundTint
+ * @see #setForegroundTintList(android.content.res.ColorStateList)
+ */
+ public ColorStateList getForegroundTintList() {
+ return mForegroundTintList;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setForegroundTintList(android.content.res.ColorStateList)}} to the foreground drawable.
+ * The default mode is {@link android.graphics.PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#FrameLayout_foregroundTintMode
+ * @see #getForegroundTintMode()
+ * @see android.graphics.drawable.Drawable#setTintMode(android.graphics.PorterDuff.Mode)
+ */
+ public void setForegroundTintMode(PorterDuff.Mode tintMode) {
+ mForegroundTintMode = tintMode;
+ mHasForegroundTintMode = true;
+
+ applyForegroundTint();
+ }
+
+ /**
+ * @return the blending mode used to apply the tint to the foreground
+ * drawable
+ * @attr ref android.R.styleable#FrameLayout_foregroundTintMode
+ * @see #setForegroundTintMode(android.graphics.PorterDuff.Mode)
+ */
+ public PorterDuff.Mode getForegroundTintMode() {
+ return mForegroundTintMode;
+ }
+
+ private void applyForegroundTint() {
+ if (mForeground != null && (mHasForegroundTint || mHasForegroundTintMode)) {
+ mForeground = mForeground.mutate();
+
+ if (mHasForegroundTint) {
+ DrawableCompat.setTint(mForeground, mForegroundTintList.
+ getColorForState(getDrawableState(), Color.TRANSPARENT));
+ }
+
+ if (mHasForegroundTintMode) {
+ DrawableCompat.setTintMode(mForeground, mForegroundTintMode);
+ }
+ }
+ }
+
+ int getPaddingLeftWithForeground() {
+ return mForegroundInPadding ? Math.max(getPaddingLeft(), mForegroundPaddingLeft) :
+ getPaddingLeft() + mForegroundPaddingLeft;
+ }
+
+ int getPaddingRightWithForeground() {
+ return mForegroundInPadding ? Math.max(getPaddingRight(), mForegroundPaddingRight) :
+ getPaddingRight() + mForegroundPaddingRight;
+ }
+
+ private int getPaddingTopWithForeground() {
+ return mForegroundInPadding ? Math.max(getPaddingTop(), mForegroundPaddingTop) :
+ getPaddingTop() + mForegroundPaddingTop;
+ }
+
+ private int getPaddingBottomWithForeground() {
+ return mForegroundInPadding ? Math.max(getPaddingBottom(), mForegroundPaddingBottom) :
+ getPaddingBottom() + mForegroundPaddingBottom;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int count = getChildCount();
+
+ final boolean measureMatchParentChildren =
+ MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
+ MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
+ mMatchParentChildren.clear();
+
+ int maxHeight = 0;
+ int maxWidth = 0;
+ int childState = 0;
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (mMeasureAllChildren || child.getVisibility() != GONE) {
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
+ maxWidth = Math.max(maxWidth,
+ child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+ maxHeight = Math.max(maxHeight,
+ child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+
+ //combine
+ childState = childState | ViewCompat.getMeasuredState(child);
+
+ if (measureMatchParentChildren) {
+ if (lp.width == LayoutParams.MATCH_PARENT ||
+ lp.height == LayoutParams.MATCH_PARENT) {
+ mMatchParentChildren.add(child);
+ }
+ }
+ }
+ }
+
+ // Account for padding too
+ maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
+ maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
+
+ // Check against our minimum height and width
+ maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+ maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+ // Check against our foreground's minimum height and width
+ final Drawable drawable = getForeground();
+ if (drawable != null) {
+ maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
+ maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
+ }
+
+ setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
+ resolveSizeAndState(maxHeight, heightMeasureSpec, childState << 16));
+
+ count = mMatchParentChildren.size();
+ if (count > 1) {
+ for (int i = 0; i < count; i++) {
+ final View child = mMatchParentChildren.get(i);
+
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ int childWidthMeasureSpec;
+ int childHeightMeasureSpec;
+
+ if (lp.width == LayoutParams.MATCH_PARENT) {
+ childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
+ getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
+ lp.leftMargin - lp.rightMargin,
+ MeasureSpec.EXACTLY);
+ } else {
+ childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+ getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
+ lp.leftMargin + lp.rightMargin,
+ lp.width);
+ }
+
+ if (lp.height == LayoutParams.MATCH_PARENT) {
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
+ getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
+ lp.topMargin - lp.bottomMargin,
+ MeasureSpec.EXACTLY);
+ } else {
+ childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
+ getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
+ lp.topMargin + lp.bottomMargin,
+ lp.height);
+ }
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+ }
+ }
+
+ /**
+ * Merge two states as returned by {@link #getMeasuredState()}.
+ * @param curState The current state as returned from a view or the result
+ * of combining multiple views.
+ * @param newState The new view state to combine.
+ * @return Returns a new integer reflecting the combination of the two
+ * states.
+ */
+ public static int combineMeasuredStates(int curState, int newState) {
+ return curState | newState;
+ }
+
+ /**
+ * Utility to reconcile a desired size and state, with constraints imposed
+ * by a MeasureSpec. Will take the desired size, unless a different size
+ * is imposed by the constraints. The returned value is a compound integer,
+ * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
+ * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
+ * size is smaller than the size the view wants to be.
+ *
+ * @param size How big the view wants to be
+ * @param measureSpec Constraints imposed by the parent
+ * @return Size information bit mask as defined by
+ * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
+ */
+ public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
+ int result = size;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+ switch (specMode) {
+ case MeasureSpec.UNSPECIFIED:
+ result = size;
+ break;
+ case MeasureSpec.AT_MOST:
+ if (specSize < size) {
+ result = specSize | 0x01000000;
+ } else {
+ result = size;
+ }
+ break;
+ case MeasureSpec.EXACTLY:
+ result = specSize;
+ break;
+ }
+ return result | (childMeasuredState&0xff000000);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ layoutChildren(left, top, right, bottom, false /* no force left gravity */);
+ }
+
+ @SuppressLint("RtlHardcoded")
+ void layoutChildren(int left, int top, int right, int bottom,
+ boolean forceLeftGravity) {
+ final int count = getChildCount();
+
+ final int parentLeft = getPaddingLeftWithForeground();
+ final int parentRight = right - left - getPaddingRightWithForeground();
+
+ final int parentTop = getPaddingTopWithForeground();
+ final int parentBottom = bottom - top - getPaddingBottomWithForeground();
+
+ mForegroundBoundsChanged = true;
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
+
+ final int width = child.getMeasuredWidth();
+ final int height = child.getMeasuredHeight();
+
+ int childLeft;
+ int childTop;
+
+ int gravity = lp.gravity;
+ if (gravity == -1) {
+ gravity = DEFAULT_CHILD_GRAVITY;
+ }
+
+ final int absoluteGravity;
+
+ final int layoutDirection = ViewCompat.getLayoutDirection(this);
+ absoluteGravity = GravityCompat.getAbsoluteGravity(gravity, layoutDirection);
+
+ final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
+ lp.leftMargin - lp.rightMargin;
+ break;
+ case Gravity.RIGHT:
+ if (!forceLeftGravity) {
+ childLeft = parentRight - width - lp.rightMargin;
+ break;
+ }
+ case Gravity.LEFT:
+ default:
+ childLeft = parentLeft + lp.leftMargin;
+ }
+
+ switch (verticalGravity) {
+ case Gravity.TOP:
+ childTop = parentTop + lp.topMargin;
+ break;
+ case Gravity.CENTER_VERTICAL:
+ childTop = parentTop + (parentBottom - parentTop - height) / 2 +
+ lp.topMargin - lp.bottomMargin;
+ break;
+ case Gravity.BOTTOM:
+ childTop = parentBottom - height - lp.bottomMargin;
+ break;
+ default:
+ childTop = parentTop + lp.topMargin;
+ }
+
+ child.layout(childLeft, childTop, childLeft + width, childTop + height);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mForegroundBoundsChanged = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ super.draw(canvas);
+
+ if (mForeground != null) {
+ final Drawable foreground = mForeground;
+
+ if (mForegroundBoundsChanged) {
+ mForegroundBoundsChanged = false;
+ final Rect selfBounds = mSelfBounds;
+ final Rect overlayBounds = mOverlayBounds;
+
+ final int w = getRight() - getLeft();
+ final int h = getBottom() - getTop();
+
+ if (mForegroundInPadding) {
+ selfBounds.set(0, 0, w, h);
+ } else {
+ selfBounds.set(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - getPaddingBottom());
+ }
+
+ final int layoutDirection = ViewCompat.getLayoutDirection(this);
+ GravityCompat.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
+ foreground.getIntrinsicHeight(), selfBounds, overlayBounds,
+ layoutDirection);
+
+
+ foreground.setBounds(overlayBounds);
+ }
+
+ foreground.draw(canvas);
+ }
+ }
+
+
+ /**
+ * Sets whether to consider all children, or just those in
+ * the VISIBLE or INVISIBLE state, when measuring. Defaults to false.
+ *
+ * @param measureAll true to consider children marked GONE, false otherwise.
+ * Default value is false.
+ *
+ * @attr ref android.R.styleable#FrameLayout_measureAllChildren
+ */
+ public void setMeasureAllChildren(boolean measureAll) {
+ mMeasureAllChildren = measureAll;
+ }
+
+ /**
+ * Determines whether all children, or just those in the VISIBLE or
+ * INVISIBLE state, are considered when measuring.
+ *
+ * @return Whether all children are considered when measuring.
+ *
+ * @deprecated This method is deprecated in favor of
+ * {@link #getMeasureAllChildren() getMeasureAllChildren()}, which was
+ * renamed for consistency with
+ * {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}.
+ */
+ @Deprecated
+ public boolean getConsiderGoneChildrenWhenMeasuring() {
+ return getMeasureAllChildren();
+ }
+
+ /**
+ * Determines whether all children, or just those in the VISIBLE or
+ * INVISIBLE state, are considered when measuring.
+ *
+ * @return Whether all children are considered when measuring.
+ */
+ public boolean getMeasureAllChildren() {
+ return mMeasureAllChildren;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new FrameLayout.LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean checkLayoutParams(LayoutParams p) {
+ return p != null && p instanceof FrameLayout.LayoutParams;
+ }
+
+ @Override
+ protected FrameLayout.LayoutParams generateLayoutParams(LayoutParams p) {
+ return new FrameLayout.LayoutParams(p);
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(FrameLayout.class.getName());
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(FrameLayout.class.getName());
+ }
+
+}
diff --git a/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/ViewUtils.java b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/ViewUtils.java
new file mode 100644
index 0000000..c0a288e
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/java/com/toaker/circularreveal/sample/widget/ViewUtils.java
@@ -0,0 +1,78 @@
+package com.toaker.circularreveal.sample.widget;
+
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.IdRes;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+public class ViewUtils {
+
+ /**
+ * Sets background, independence of API Level
+ *
+ * @param view target
+ * @param drawable background
+ */
+ public static void setBackground(View view, Drawable drawable){
+ if(Build.VERSION.SDK_INT > 16) {
+ view.setBackground(drawable);
+ }else{
+ view.setBackgroundDrawable(drawable);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T findView(@NonNull Fragment destination, @IdRes int res){
+ return (T) destination.getView().findViewById(res);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T findView(@NonNull Activity destination, @IdRes int res){
+ return (T) destination.findViewById(res);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T findView(@NonNull View destination, @IdRes int res){
+ return (T) destination.findViewById(res);
+ }
+
+ public static void removeGlobalListeners(View target, ViewTreeObserver.OnGlobalLayoutListener listener){
+ if(Build.VERSION.SDK_INT > 16) {
+ target.getViewTreeObserver()
+ .removeOnGlobalLayoutListener(listener);
+ }else{
+ target.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
+ }
+ }
+
+ /**
+ * Make view visible or invisible
+ */
+ public static void setVisibility(View target, boolean visible){
+ target.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ /**
+ * @param target The target view
+ * @param visible false if need to set {@link android.view.View#GONE} flag
+ */
+ public static void setVisibilityWithGoneFlag(View target, boolean visible){
+ target.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Checks view is visible or not
+ *
+ * @param view to check
+ * @return true if {@link android.view.View#getVisibility()}
+ * equals to {@link android.view.View#VISIBLE}
+ */
+ public static boolean isVisible(View view){
+ return view.getVisibility() == View.VISIBLE;
+ }
+
+}
diff --git a/circular-reveal-demo/sample/src/main/res/color/overlay_color.xml b/circular-reveal-demo/sample/src/main/res/color/overlay_color.xml
new file mode 100644
index 0000000..64a06e1
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/res/color/overlay_color.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-hdpi/ic_launcher.png b/circular-reveal-demo/sample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png b/circular-reveal-demo/sample/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png
new file mode 100644
index 0000000..cd16fdd
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-mdpi/ic_launcher.png b/circular-reveal-demo/sample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/floatingactionbutton_shadow_layer.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/floatingactionbutton_shadow_layer.png
new file mode 100644
index 0000000..38f0330
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/floatingactionbutton_shadow_layer.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/ic_launcher.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png
new file mode 100644
index 0000000..5f89fc2
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_1.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_1.png
new file mode 100644
index 0000000..44b6ef2
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_1.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_2.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_2.png
new file mode 100644
index 0000000..2e8a4d6
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_2.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_3.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_3.png
new file mode 100644
index 0000000..c5dff66
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_3.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_4.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_4.png
new file mode 100644
index 0000000..78fd02b
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_4.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_5.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_5.png
new file mode 100644
index 0000000..68b9476
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_5.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_6.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_6.png
new file mode 100644
index 0000000..bf355a5
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_6.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_7.png b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_7.png
new file mode 100644
index 0000000..3034e0a
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xhdpi/icn_7.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xxhdpi/ic_launcher.png b/circular-reveal-demo/sample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png b/circular-reveal-demo/sample/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png
new file mode 100644
index 0000000..72128fe
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/layout/activity_main.xml b/circular-reveal-demo/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..9239d40
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/circular-reveal-demo/sample/src/main/res/layout/card_item.xml b/circular-reveal-demo/sample/src/main/res/layout/card_item.xml
new file mode 100644
index 0000000..39defb3
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/res/layout/card_item.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/circular-reveal-demo/sample/src/main/res/menu/menu_main.xml b/circular-reveal-demo/sample/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..b1cb908
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/res/menu/menu_main.xml
@@ -0,0 +1,6 @@
+
diff --git a/circular-reveal-demo/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/circular-reveal-demo/sample/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/circular-reveal-demo/sample/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/circular-reveal-demo/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/circular-reveal-demo/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/circular-reveal-demo/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/circular-reveal-demo/sample/src/main/res/values-w820dp/dimens.xml b/circular-reveal-demo/sample/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/circular-reveal-demo/sample/src/main/res/values/dimens.xml b/circular-reveal-demo/sample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/circular-reveal-demo/sample/src/main/res/values/strings.xml b/circular-reveal-demo/sample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..70444fb
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+
+ CircualRevealSample
+ Hello world!
+ Settings
+
+
diff --git a/circular-reveal-demo/sample/src/main/res/values/styles.xml b/circular-reveal-demo/sample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..1098e6a
--- /dev/null
+++ b/circular-reveal-demo/sample/src/main/res/values/styles.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 8dp
+ 24dp
+
+
+
+
+
diff --git a/circular-reveal-demo/settings.gradle b/circular-reveal-demo/settings.gradle
new file mode 100644
index 0000000..9d9eae5
--- /dev/null
+++ b/circular-reveal-demo/settings.gradle
@@ -0,0 +1 @@
+include ':Sample',':circualreveal'
diff --git a/circular-reveal-demo/source/Sample.gif b/circular-reveal-demo/source/Sample.gif
new file mode 100644
index 0000000..c47e2c3
Binary files /dev/null and b/circular-reveal-demo/source/Sample.gif differ
diff --git a/circular-reveal-demo/source/Sample.jpg b/circular-reveal-demo/source/Sample.jpg
new file mode 100644
index 0000000..bd2cdef
Binary files /dev/null and b/circular-reveal-demo/source/Sample.jpg differ
diff --git a/circular-reveal-demo/source/Sample.mp4 b/circular-reveal-demo/source/Sample.mp4
new file mode 100644
index 0000000..18da63a
Binary files /dev/null and b/circular-reveal-demo/source/Sample.mp4 differ
diff --git a/common/codeformat.xml b/common/codeformat.xml
index 6f1adb3..0dcdc3f 100644
--- a/common/codeformat.xml
+++ b/common/codeformat.xml
@@ -1,291 +1,291 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/codetemplates.xml b/common/codetemplates.xml
index b8b104b..190a4db 100644
--- a/common/codetemplates.xml
+++ b/common/codetemplates.xml
@@ -1,33 +1,33 @@
-/**
- * @return the ${bare_field_name}
- *//**
- * @param ${param} the ${bare_field_name} to set
- *//**
- * ${tags}
- *//**
- *
- *//**
- * @author ${user}
- *
- * ${tags}
- *//**
- *
- *//**
- * ${tags}
- *//* (non-Javadoc)
- * ${see_to_overridden}
- *//**
- * ${tags}
- * ${see_to_target}
- */${filecomment}
-${package_declaration}
-
-${typecomment}
-${type_declaration}
-
-
-
-// ${todo} Auto-generated catch block
-${exception_var}.printStackTrace();// ${todo} Auto-generated method stub
-${body_statement}${body_statement}
-// ${todo} Auto-generated constructor stubreturn ${field};${field} = ${param};
+/**
+ * @return the ${bare_field_name}
+ *//**
+ * @param ${param} the ${bare_field_name} to set
+ *//**
+ * ${tags}
+ *//**
+ *
+ *//**
+ * @author ${user}
+ *
+ * ${tags}
+ *//**
+ *
+ *//**
+ * ${tags}
+ *//* (non-Javadoc)
+ * ${see_to_overridden}
+ *//**
+ * ${tags}
+ * ${see_to_target}
+ */${filecomment}
+${package_declaration}
+
+${typecomment}
+${type_declaration}
+
+
+
+// ${todo} Auto-generated catch block
+${exception_var}.printStackTrace();// ${todo} Auto-generated method stub
+${body_statement}${body_statement}
+// ${todo} Auto-generated constructor stubreturn ${field};${field} = ${param};
diff --git a/countly-sdk-android-demo/.gitignore b/countly-sdk-android-demo/.gitignore
new file mode 100644
index 0000000..c6cbe56
--- /dev/null
+++ b/countly-sdk-android-demo/.gitignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/countly-sdk-android-demo/README.md b/countly-sdk-android-demo/README.md
new file mode 100644
index 0000000..4101286
--- /dev/null
+++ b/countly-sdk-android-demo/README.md
@@ -0,0 +1,12 @@
+countly-sdk-android Demo
+====================
+###1. Demo Download
+[本地下载](apk/countly-sdk-android-demo.apk?raw=true "点击下载到本地")
+
+###2. Screenshot
+
+
+###3. Document
+[countly-sdk-android使用指南](http://resources.count.ly/v2.0/docs/countly-sdk-for-android)
+
+[countly-server试用](http://cloud.count.ly)
diff --git a/countly-sdk-android-demo/apk/countly-sdk-android-demo.apk b/countly-sdk-android-demo/apk/countly-sdk-android-demo.apk
new file mode 100644
index 0000000..89d6df7
Binary files /dev/null and b/countly-sdk-android-demo/apk/countly-sdk-android-demo.apk differ
diff --git a/countly-sdk-android-demo/apk/demo.png b/countly-sdk-android-demo/apk/demo.png
new file mode 100644
index 0000000..2d9395a
Binary files /dev/null and b/countly-sdk-android-demo/apk/demo.png differ
diff --git a/countly-sdk-android-demo/app/.gitignore b/countly-sdk-android-demo/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/countly-sdk-android-demo/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/countly-sdk-android-demo/app/build.gradle b/countly-sdk-android-demo/app/build.gradle
new file mode 100644
index 0000000..eaf3198
--- /dev/null
+++ b/countly-sdk-android-demo/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ applicationId "com.greens1995.countly_sdk_analysis"
+ minSdkVersion 15
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:23.3.0'
+}
diff --git a/countly-sdk-android-demo/app/libs/sdk-16.02.01.jar b/countly-sdk-android-demo/app/libs/sdk-16.02.01.jar
new file mode 100644
index 0000000..b14dd2f
Binary files /dev/null and b/countly-sdk-android-demo/app/libs/sdk-16.02.01.jar differ
diff --git a/countly-sdk-android-demo/app/proguard-rules.pro b/countly-sdk-android-demo/app/proguard-rules.pro
new file mode 100644
index 0000000..5f191e5
--- /dev/null
+++ b/countly-sdk-android-demo/app/proguard-rules.pro
@@ -0,0 +1,18 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/macbookair/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+-keep class org.openudid.** { *; }
\ No newline at end of file
diff --git a/countly-sdk-android-demo/app/src/androidTest/java/com/greens1995/countly_sdk_analysis/ApplicationTest.java b/countly-sdk-android-demo/app/src/androidTest/java/com/greens1995/countly_sdk_analysis/ApplicationTest.java
new file mode 100644
index 0000000..31c296e
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/androidTest/java/com/greens1995/countly_sdk_analysis/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.greens1995.countly_sdk_analysis;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/countly-sdk-android-demo/app/src/main/AndroidManifest.xml b/countly-sdk-android-demo/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..904e0b2
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/countly-sdk-android-demo/app/src/main/java/com/greens1995/countly_sdk_analysis/MainActivity.java b/countly-sdk-android-demo/app/src/main/java/com/greens1995/countly_sdk_analysis/MainActivity.java
new file mode 100644
index 0000000..75b6f74
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/main/java/com/greens1995/countly_sdk_analysis/MainActivity.java
@@ -0,0 +1,137 @@
+package com.greens1995.countly_sdk_analysis;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import ly.count.android.sdk.Countly;
+
+
+public class MainActivity extends Activity {
+ private Activity activity;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ activity = this;
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ Countly.onCreate(this);
+
+ Button eventNormal = (Button) findViewById(R.id.event_normal);
+ eventNormal.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Countly.sharedInstance().recordEvent("test", 1);
+ }
+ });
+
+ Button eventSum = (Button) findViewById(R.id.event_sum);
+ eventSum.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Countly.sharedInstance().recordEvent("test2", 1, 2);
+ }
+ });
+
+ Button eventSegment = (Button) findViewById(R.id.event_segment);
+ eventSegment.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ final Map seg = new HashMap<>();
+ seg.put(getPackageName() + "NetworkType", "3G");
+ Countly.sharedInstance().recordEvent("login", seg, 1);
+ }
+ });
+
+ Button eventSegmentSum = (Button) findViewById(R.id.event_segment_sum);
+ eventSegmentSum.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ final Map seg = new HashMap<>();
+ seg.put(getPackageName() + "NetworkType", "3G");
+ Countly.sharedInstance().recordEvent("login", seg, 1,3);
+ }
+ });
+
+ Button button1 = (Button) findViewById(R.id.runtime);
+ button1.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Countly.sharedInstance().addCrashLog("Button 1 pressed");
+ Countly.sharedInstance().crashTest(4);
+ }
+ });
+
+ Button button2 = (Button) findViewById(R.id.nullpointer);
+ button2.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Countly.sharedInstance().addCrashLog("Button 2 pressed");
+ Countly.sharedInstance().crashTest(5);
+ }
+ });
+
+ Button button3 = (Button) findViewById(R.id.division0);
+ button3.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Countly.sharedInstance().addCrashLog("Button 3 pressed");
+ Countly.sharedInstance().crashTest(2);
+ }
+ });
+
+
+ Button button5 = (Button) findViewById(R.id.stackoverflow);
+ button5.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Countly.sharedInstance().addCrashLog("Button 4 pressed");
+ Countly.sharedInstance().crashTest(1);
+ }
+ });
+
+ Button button6 = (Button) findViewById(R.id.handled);
+ button6.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Countly.sharedInstance().addCrashLog("Button 5 pressed");
+ try {
+ Countly.sharedInstance().crashTest(5);
+ }
+ catch(Exception e){
+ Countly.sharedInstance().logException(e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onStart()
+ {
+ super.onStart();
+ Countly.sharedInstance().onStart(this);
+ }
+
+ @Override
+ public void onStop()
+ {
+ Countly.sharedInstance().onStop();
+ super.onStop();
+ }
+
+}
diff --git a/countly-sdk-android-demo/app/src/main/java/com/greens1995/countly_sdk_analysis/MyApplication.java b/countly-sdk-android-demo/app/src/main/java/com/greens1995/countly_sdk_analysis/MyApplication.java
new file mode 100644
index 0000000..793b376
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/main/java/com/greens1995/countly_sdk_analysis/MyApplication.java
@@ -0,0 +1,73 @@
+package com.greens1995.countly_sdk_analysis;
+
+import android.app.Application;
+
+import java.util.HashMap;
+
+import ly.count.android.sdk.Countly;
+
+public class MyApplication extends Application {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+// double latitude = 12;
+// double longitude = 22;
+ Countly.sharedInstance().setLoggingEnabled(true);
+ /** You should use cloud.count.ly instead of YOUR_SERVER for the line below if you are using Countly Cloud service */
+ Countly.sharedInstance()
+ .init(this, "http://cloud.count.ly", "73aaef1394906b4c54de28c32215fd30317ac475");
+// .setLocation(latitude, longitude);
+// .setLoggingEnabled(true);
+ setUserData(); // If UserData plugin is enabled on your server
+ enableCrashTracking();
+ }
+
+ public void setUserData(){
+ HashMap data = new HashMap();
+ data.put("name", "Firstname Lastname");
+ data.put("username", "nickname");
+ data.put("email", "test@test.com");
+ data.put("organization", "Tester");
+ data.put("phone", "+123456789");
+ data.put("gender", "M");
+ //provide url to picture
+ //data.put("picture", "http://example.com/pictures/profile_pic.png");
+ //or locally from device
+ //data.put("picturePath", "/mnt/sdcard/portrait.jpg");
+ data.put("byear", "1987");
+
+ //providing any custom key values to store with user
+ HashMap custom = new HashMap();
+ custom.put("country", "Turkey");
+ custom.put("city", "Istanbul");
+ custom.put("address", "My house 11");
+
+ //set multiple custom properties
+ Countly.userData.setUserData(data, custom);
+
+ //set custom properties by one
+ Countly.userData.setProperty("test", "test");
+
+ //increment used value by 1
+ Countly.userData.incrementBy("used", 1);
+
+ //insert value to array of unique values
+ Countly.userData.pushUniqueValue("type", "morning");
+
+ //insert multiple values to same property
+ Countly.userData.pushUniqueValue("skill", "fire");
+ Countly.userData.pushUniqueValue("skill", "earth");
+
+ Countly.userData.save();
+ }
+
+ public void enableCrashTracking(){
+ //add some custom segments, like dependency library versions
+ HashMap data = new HashMap();
+ data.put("Facebook", "3.5");
+ data.put("Admob", "6.5");
+ Countly.sharedInstance().setCustomCrashSegments(data);
+ Countly.sharedInstance().enableCrashReporting();
+ }
+}
\ No newline at end of file
diff --git a/countly-sdk-android-demo/app/src/main/res/layout/activity_main.xml b/countly-sdk-android-demo/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..938fa5e
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/countly-sdk-android-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/countly-sdk-android-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/countly-sdk-android-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/countly-sdk-android-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/countly-sdk-android-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/countly-sdk-android-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/countly-sdk-android-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/countly-sdk-android-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/countly-sdk-android-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/countly-sdk-android-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/countly-sdk-android-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/countly-sdk-android-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/countly-sdk-android-demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/countly-sdk-android-demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/countly-sdk-android-demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/countly-sdk-android-demo/app/src/main/res/values-w820dp/dimens.xml b/countly-sdk-android-demo/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/countly-sdk-android-demo/app/src/main/res/values/colors.xml b/countly-sdk-android-demo/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/countly-sdk-android-demo/app/src/main/res/values/dimens.xml b/countly-sdk-android-demo/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/countly-sdk-android-demo/app/src/main/res/values/strings.xml b/countly-sdk-android-demo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..97a5f5f
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Countly-sdk-analysis
+
diff --git a/countly-sdk-android-demo/app/src/main/res/values/styles.xml b/countly-sdk-android-demo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/countly-sdk-android-demo/app/src/test/java/com/greens1995/countly_sdk_analysis/ExampleUnitTest.java b/countly-sdk-android-demo/app/src/test/java/com/greens1995/countly_sdk_analysis/ExampleUnitTest.java
new file mode 100644
index 0000000..5bed352
--- /dev/null
+++ b/countly-sdk-android-demo/app/src/test/java/com/greens1995/countly_sdk_analysis/ExampleUnitTest.java
@@ -0,0 +1,15 @@
+package com.greens1995.countly_sdk_analysis;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/countly-sdk-android-demo/build.gradle b/countly-sdk-android-demo/build.gradle
new file mode 100644
index 0000000..e0b366a
--- /dev/null
+++ b/countly-sdk-android-demo/build.gradle
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.5.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/countly-sdk-android-demo/gradle.properties b/countly-sdk-android-demo/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/countly-sdk-android-demo/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/countly-sdk-android-demo/gradle/wrapper/gradle-wrapper.jar b/countly-sdk-android-demo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..05ef575
Binary files /dev/null and b/countly-sdk-android-demo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/countly-sdk-android-demo/gradle/wrapper/gradle-wrapper.properties b/countly-sdk-android-demo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..f23df6e
--- /dev/null
+++ b/countly-sdk-android-demo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Oct 21 11:34:03 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
diff --git a/countly-sdk-android-demo/gradlew b/countly-sdk-android-demo/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/countly-sdk-android-demo/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/countly-sdk-android-demo/gradlew.bat b/countly-sdk-android-demo/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/countly-sdk-android-demo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/countly-sdk-android-demo/settings.gradle b/countly-sdk-android-demo/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/countly-sdk-android-demo/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/discrete-seek-bar-demo/.gitignore b/discrete-seek-bar-demo/.gitignore
new file mode 100644
index 0000000..79859d1
--- /dev/null
+++ b/discrete-seek-bar-demo/.gitignore
@@ -0,0 +1,6 @@
+.gradle
+/local.properties
+/.idea
+.DS_Store
+/build
+*.iml
diff --git a/discrete-seek-bar-demo/LICENSE b/discrete-seek-bar-demo/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/discrete-seek-bar-demo/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/discrete-seek-bar-demo/README.md b/discrete-seek-bar-demo/README.md
new file mode 100644
index 0000000..f8cd40e
--- /dev/null
+++ b/discrete-seek-bar-demo/README.md
@@ -0,0 +1,13 @@
+#Discrete-Seek-Bar-Demo
+
+====================
+###1. Original Project
+[AnderWeb/discreteSeekBar](https://github.com/AnderWeb/discreteSeekBar)
+
+###2. Demo
+[local demo download](apk/discrete-seek-bar-demo.apk?raw=true "click to download")
+
+###3. Screenshot
+
+
+
diff --git a/discrete-seek-bar-demo/apk/discrete-seek-bar-demo.apk b/discrete-seek-bar-demo/apk/discrete-seek-bar-demo.apk
new file mode 100644
index 0000000..315a352
Binary files /dev/null and b/discrete-seek-bar-demo/apk/discrete-seek-bar-demo.apk differ
diff --git a/discrete-seek-bar-demo/apk/discreteseekbar.gif b/discrete-seek-bar-demo/apk/discreteseekbar.gif
new file mode 100644
index 0000000..36cad8c
Binary files /dev/null and b/discrete-seek-bar-demo/apk/discreteseekbar.gif differ
diff --git a/discrete-seek-bar-demo/build.gradle b/discrete-seek-bar-demo/build.gradle
new file mode 100644
index 0000000..1b41e87
--- /dev/null
+++ b/discrete-seek-bar-demo/build.gradle
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter{
+ url "http://jcenter.bintray.com/"
+ }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.0.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter{
+ url "http://jcenter.bintray.com/"
+ }
+ }
+}
diff --git a/discrete-seek-bar-demo/gradle.properties b/discrete-seek-bar-demo/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/discrete-seek-bar-demo/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/gradle/wrapper/gradle-wrapper.jar b/discrete-seek-bar-demo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/discrete-seek-bar-demo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/discrete-seek-bar-demo/gradle/wrapper/gradle-wrapper.properties b/discrete-seek-bar-demo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a6ad635
--- /dev/null
+++ b/discrete-seek-bar-demo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Nov 26 21:03:52 CET 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/discrete-seek-bar-demo/gradlew b/discrete-seek-bar-demo/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/discrete-seek-bar-demo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/discrete-seek-bar-demo/gradlew.bat b/discrete-seek-bar-demo/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/discrete-seek-bar-demo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/discrete-seek-bar-demo/library/.gitignore b/discrete-seek-bar-demo/library/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/discrete-seek-bar-demo/library/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/discrete-seek-bar-demo/library/build.gradle b/discrete-seek-bar-demo/library/build.gradle
new file mode 100644
index 0000000..e262961
--- /dev/null
+++ b/discrete-seek-bar-demo/library/build.gradle
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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.
+ */
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.1"
+
+ defaultConfig {
+ minSdkVersion 4
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:21.0.2'
+}
diff --git a/discrete-seek-bar-demo/library/src/main/AndroidManifest.xml b/discrete-seek-bar-demo/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6d97bb6
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/DiscreteSeekBar.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/DiscreteSeekBar.java
new file mode 100644
index 0000000..32baad6
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/DiscreteSeekBar.java
@@ -0,0 +1,983 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+import org.adw.library.widgets.discreteseekbar.internal.PopupIndicator;
+import org.adw.library.widgets.discreteseekbar.internal.compat.AnimatorCompat;
+import org.adw.library.widgets.discreteseekbar.internal.compat.SeekBarCompat;
+import org.adw.library.widgets.discreteseekbar.internal.drawable.MarkerDrawable;
+import org.adw.library.widgets.discreteseekbar.internal.drawable.ThumbDrawable;
+import org.adw.library.widgets.discreteseekbar.internal.drawable.TrackRectDrawable;
+
+import java.util.Formatter;
+import java.util.Locale;
+
+public class DiscreteSeekBar extends View {
+
+ /**
+ * Interface to propagate seekbar change event
+ */
+ public interface OnProgressChangeListener {
+ /**
+ * When the {@link DiscreteSeekBar} value changes
+ *
+ * @param seekBar The DiscreteSeekBar
+ * @param value the new value
+ * @param fromUser if the change was made from the user or not (i.e. the developer calling {@link #setProgress(int)}
+ */
+ public void onProgressChanged(DiscreteSeekBar seekBar, int value, boolean fromUser);
+
+ public void onStartTrackingTouch(DiscreteSeekBar seekBar);
+ public void onStopTrackingTouch(DiscreteSeekBar seekBar);
+ }
+
+ /**
+ * Interface to transform the current internal value of this DiscreteSeekBar to anther one for the visualization.
+ *
+ * This will be used on the floating bubble to display a different value if needed.
+ *
+ * Using this in conjunction with {@link #setIndicatorFormatter(String)} you will be able to manipulate the
+ * value seen by the user
+ *
+ * @see #setIndicatorFormatter(String)
+ * @see #setNumericTransformer(DiscreteSeekBar.NumericTransformer)
+ */
+ public static abstract class NumericTransformer {
+ /**
+ * Return the desired value to be shown to the user.
+ * This value will be formatted using the format specified by {@link #setIndicatorFormatter} before displaying it
+ *
+ * @param value The value to be transformed
+ * @return The transformed int
+ */
+ public abstract int transform(int value);
+
+ /**
+ * Return the desired value to be shown to the user.
+ * This value will be displayed 'as is' without further formatting.
+ *
+ * @param value The value to be transformed
+ * @return A formatted string
+ */
+ public String transformToString(int value) {
+ return String.valueOf(value);
+ }
+
+ /**
+ * Used to indicate which transform will be used. If this method returns true,
+ * {@link #transformToString(int)} will be used, otherwise {@link #transform(int)}
+ * will be used
+ */
+ public boolean useStringTransform() {
+ return false;
+ }
+ }
+
+
+ private static class DefaultNumericTransformer extends NumericTransformer {
+
+ @Override
+ public int transform(int value) {
+ return value;
+ }
+ }
+
+
+ private static final boolean isLollipopOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+ //We want to always use a formatter so the indicator numbers are "translated" to specific locales.
+ private static final String DEFAULT_FORMATTER = "%d";
+
+ private static final int PRESSED_STATE = android.R.attr.state_pressed;
+ private static final int FOCUSED_STATE = android.R.attr.state_focused;
+ private static final int PROGRESS_ANIMATION_DURATION = 250;
+ private static final int INDICATOR_DELAY_FOR_TAPS = 150;
+ private ThumbDrawable mThumb;
+ private Drawable mTrack;
+ private Drawable mScrubber;
+ private Drawable mRipple;
+
+ private int mTrackHeight;
+ private int mScrubberHeight;
+ private int mAddedTouchBounds;
+
+ private int mMax;
+ private int mMin;
+ private int mValue;
+ private int mKeyProgressIncrement = 1;
+ private boolean mMirrorForRtl = false;
+ private boolean mAllowTrackClick = true;
+ //We use our own Formatter to avoid creating new instances on every progress change
+ Formatter mFormatter;
+ private String mIndicatorFormatter;
+ private NumericTransformer mNumericTransformer;
+ private StringBuilder mFormatBuilder;
+ private OnProgressChangeListener mPublicChangeListener;
+ private boolean mIsDragging;
+ private int mDragOffset;
+
+ private Rect mInvalidateRect = new Rect();
+ private Rect mTempRect = new Rect();
+ private PopupIndicator mIndicator;
+ private AnimatorCompat mPositionAnimator;
+ private float mAnimationPosition;
+ private int mAnimationTarget;
+ private float mDownX;
+ private float mTouchSlop;
+
+ public DiscreteSeekBar(Context context) {
+ this(context, null);
+ }
+
+ public DiscreteSeekBar(Context context, AttributeSet attrs) {
+ this(context, attrs, R.style.DefaultSeekBar);
+ }
+
+ public DiscreteSeekBar(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setFocusable(true);
+ setWillNotDraw(false);
+
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ float density = context.getResources().getDisplayMetrics().density;
+ mTrackHeight = (int) (1 * density);
+ mScrubberHeight = (int) (4 * density);
+ int thumbSize = (int) (density * ThumbDrawable.DEFAULT_SIZE_DP);
+
+ //Extra pixels for a touch area of 48dp
+ int touchBounds = (int) (density * 32);
+ mAddedTouchBounds = (touchBounds - thumbSize) / 2;
+
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscreteSeekBar,
+ R.attr.discreteSeekBarStyle, defStyle);
+
+ int max = 100;
+ int min = 0;
+ int value = 0;
+ mMirrorForRtl = a.getBoolean(R.styleable.DiscreteSeekBar_dsb_mirrorForRtl, mMirrorForRtl);
+ mAllowTrackClick = a.getBoolean(R.styleable.DiscreteSeekBar_dsb_allowTrackClickToDrag, mAllowTrackClick);
+
+ int indexMax = R.styleable.DiscreteSeekBar_dsb_max;
+ int indexMin = R.styleable.DiscreteSeekBar_dsb_min;
+ int indexValue = R.styleable.DiscreteSeekBar_dsb_value;
+ final TypedValue out = new TypedValue();
+ //Not sure why, but we wanted to be able to use dimensions here...
+ if (a.getValue(indexMax, out)) {
+ if (out.type == TypedValue.TYPE_DIMENSION) {
+ max = a.getDimensionPixelSize(indexMax, max);
+ } else {
+ max = a.getInteger(indexMax, max);
+ }
+ }
+ if (a.getValue(indexMin, out)) {
+ if (out.type == TypedValue.TYPE_DIMENSION) {
+ min = a.getDimensionPixelSize(indexMin, min);
+ } else {
+ min = a.getInteger(indexMin, min);
+ }
+ }
+ if (a.getValue(indexValue, out)) {
+ if (out.type == TypedValue.TYPE_DIMENSION) {
+ value = a.getDimensionPixelSize(indexValue, value);
+ } else {
+ value = a.getInteger(indexValue, value);
+ }
+ }
+
+ mMin = min;
+ mMax = Math.max(min + 1, max);
+ mValue = Math.max(min, Math.min(max, value));
+ updateKeyboardRange();
+
+ mIndicatorFormatter = a.getString(R.styleable.DiscreteSeekBar_dsb_indicatorFormatter);
+
+ ColorStateList trackColor = a.getColorStateList(R.styleable.DiscreteSeekBar_dsb_trackColor);
+ ColorStateList progressColor = a.getColorStateList(R.styleable.DiscreteSeekBar_dsb_progressColor);
+ ColorStateList rippleColor = a.getColorStateList(R.styleable.DiscreteSeekBar_dsb_rippleColor);
+ boolean editMode = isInEditMode();
+ if (editMode && rippleColor == null) {
+ rippleColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{Color.DKGRAY});
+ }
+ if (editMode && trackColor == null) {
+ trackColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{Color.GRAY});
+ }
+ if (editMode && progressColor == null) {
+ progressColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{0xff009688});
+ }
+ mRipple = SeekBarCompat.getRipple(rippleColor);
+ if (isLollipopOrGreater) {
+ SeekBarCompat.setBackground(this, mRipple);
+ } else {
+ mRipple.setCallback(this);
+ }
+
+
+ TrackRectDrawable shapeDrawable = new TrackRectDrawable(trackColor);
+ mTrack = shapeDrawable;
+ mTrack.setCallback(this);
+
+ shapeDrawable = new TrackRectDrawable(progressColor);
+ mScrubber = shapeDrawable;
+ mScrubber.setCallback(this);
+
+ ThumbDrawable thumbDrawable = new ThumbDrawable(progressColor, thumbSize);
+ mThumb = thumbDrawable;
+ mThumb.setCallback(this);
+ mThumb.setBounds(0, 0, mThumb.getIntrinsicWidth(), mThumb.getIntrinsicHeight());
+
+
+ if (!editMode) {
+ mIndicator = new PopupIndicator(context, attrs, defStyle, convertValueToMessage(mMax));
+ mIndicator.setListener(mFloaterListener);
+ }
+ a.recycle();
+
+ setNumericTransformer(new DefaultNumericTransformer());
+
+ }
+
+ /**
+ * Sets the current Indicator formatter string
+ *
+ * @param formatter
+ * @see String#format(String, Object...)
+ * @see #setNumericTransformer(DiscreteSeekBar.NumericTransformer)
+ */
+ public void setIndicatorFormatter(@Nullable String formatter) {
+ mIndicatorFormatter = formatter;
+ updateProgressMessage(mValue);
+ }
+
+ /**
+ * Sets the current {@link DiscreteSeekBar.NumericTransformer}
+ *
+ * @param transformer
+ * @see #getNumericTransformer()
+ */
+ public void setNumericTransformer(@Nullable NumericTransformer transformer) {
+ mNumericTransformer = transformer != null ? transformer : new DefaultNumericTransformer();
+ //We need to refresh the PopupIndicator view
+ if (!isInEditMode()) {
+ if (mNumericTransformer.useStringTransform()) {
+ mIndicator.updateSizes(mNumericTransformer.transformToString(mMax));
+ } else {
+ mIndicator.updateSizes(convertValueToMessage(mNumericTransformer.transform(mMax)));
+ }
+ }
+ updateProgressMessage(mValue);
+ }
+
+ /**
+ * Retrieves the current {@link DiscreteSeekBar.NumericTransformer}
+ *
+ * @return NumericTransformer
+ * @see #setNumericTransformer
+ */
+ public NumericTransformer getNumericTransformer() {
+ return mNumericTransformer;
+ }
+
+ /**
+ * Sets the maximum value for this DiscreteSeekBar
+ * if the supplied argument is smaller than the Current MIN value,
+ * the MIN value will be set to MAX-1
+ *
+ *
+ * Also if the current progress is out of the new range, it will be set to MIN
+ *
+ *
+ * @param max
+ * @see #setMin(int)
+ * @see #setProgress(int)
+ */
+ public void setMax(int max) {
+ mMax = max;
+ if (mMax < mMin) {
+ setMin(mMax - 1);
+ }
+ updateKeyboardRange();
+
+ if (mValue < mMin || mValue > mMax) {
+ setProgress(mMin);
+ }
+ }
+
+ public int getMax() {
+ return mMax;
+ }
+
+ /**
+ * Sets the minimum value for this DiscreteSeekBar
+ * if the supplied argument is bigger than the Current MAX value,
+ * the MAX value will be set to MIN+1
+ *
+ * Also if the current progress is out of the new range, it will be set to MIN
+ *
+ *
+ * @param min
+ * @see #setMax(int)
+ * @see #setProgress(int)
+ */
+ public void setMin(int min) {
+ mMin = min;
+ if (mMin > mMax) {
+ setMax(mMin + 1);
+ }
+ updateKeyboardRange();
+
+ if (mValue < mMin || mValue > mMax) {
+ setProgress(mMin);
+ }
+ }
+
+ public int getMin() {
+ return mMin;
+ }
+
+ /**
+ * Sets the current progress for this DiscreteSeekBar
+ * The supplied argument will be capped to the current MIN-MAX range
+ *
+ * @param progress
+ * @see #setMax(int)
+ * @see #setMin(int)
+ */
+ public void setProgress(int progress) {
+ setProgress(progress, false);
+ }
+
+ private void setProgress(int value, boolean fromUser) {
+ value = Math.max(mMin, Math.min(mMax, value));
+ if (isAnimationRunning()) {
+ mPositionAnimator.cancel();
+ }
+
+ if (mValue != value) {
+ mValue = value;
+ notifyProgress(value, fromUser);
+ updateProgressMessage(value);
+ updateThumbPosFromCurrentProgress();
+ }
+ }
+
+ /**
+ * Get the current progress
+ *
+ * @return the current progress :-P
+ */
+ public int getProgress() {
+ return mValue;
+ }
+
+ /**
+ * Sets a listener to receive notifications of changes to the DiscreteSeekBar's progress level. Also
+ * provides notifications of when the DiscreteSeekBar shows/hides the bubble indicator.
+ *
+ * @param listener The seek bar notification listener
+ * @see DiscreteSeekBar.OnProgressChangeListener
+ */
+ public void setOnProgressChangeListener(OnProgressChangeListener listener) {
+ mPublicChangeListener = listener;
+ }
+
+ /**
+ * Sets the color of the seek thumb, as well as the color of the popup indicator.
+ *
+ * @param startColor The color the seek thumb will be changed to
+ * @param endColor The color the popup indicator will be changed to
+ */
+ public void setThumbColor(int startColor, int endColor) {
+ mThumb.setColorStateList(ColorStateList.valueOf(startColor));
+ mIndicator.setColors(startColor, endColor);
+ }
+
+ /**
+ * Sets the color of the seekbar scrubber
+ *
+ * @param color The color the track will be changed to
+ */
+ public void setScrubberColor(int color) {
+ mScrubber.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ }
+
+ private void notifyProgress(int value, boolean fromUser) {
+ if (mPublicChangeListener != null) {
+ mPublicChangeListener.onProgressChanged(DiscreteSeekBar.this, value, fromUser);
+ }
+ onValueChanged(value);
+ }
+
+ private void notifyBubble(boolean open) {
+ if (open) {
+ onShowBubble();
+ } else {
+ onHideBubble();
+ }
+ }
+
+ /**
+ * When the {@link DiscreteSeekBar} enters pressed or focused state
+ * the bubble with the value will be shown, and this method called
+ *
+ * Subclasses may override this to add functionality around this event
+ *
+ */
+ protected void onShowBubble() {
+ }
+
+ /**
+ * When the {@link DiscreteSeekBar} exits pressed or focused state
+ * the bubble with the value will be hidden, and this method called
+ *
+ * Subclasses may override this to add functionality around this event
+ *
+ */
+ protected void onHideBubble() {
+ }
+
+ /**
+ * When the {@link DiscreteSeekBar} value changes this method is called
+ *
+ * Subclasses may override this to add functionality around this event
+ * without having to specify a {@link DiscreteSeekBar.OnProgressChangeListener}
+ *
+ */
+ protected void onValueChanged(int value) {
+ }
+
+ private void updateKeyboardRange() {
+ int range = mMax - mMin;
+ if ((mKeyProgressIncrement == 0) || (range / mKeyProgressIncrement > 20)) {
+ // It will take the user too long to change this via keys, change it
+ // to something more reasonable
+ mKeyProgressIncrement = Math.max(1, Math.round((float) range / 20));
+ }
+ }
+
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int height = mThumb.getIntrinsicHeight() + getPaddingTop() + getPaddingBottom();
+ height += (mAddedTouchBounds * 2);
+ setMeasuredDimension(widthSize, height);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (changed) {
+ removeCallbacks(mShowIndicatorRunnable);
+ if (!isInEditMode()) {
+ mIndicator.dismissComplete();
+ }
+ updateFromDrawableState();
+ }
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ super.scheduleDrawable(who, what, when);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ int thumbWidth = mThumb.getIntrinsicWidth();
+ int thumbHeight = mThumb.getIntrinsicHeight();
+ int addedThumb = mAddedTouchBounds;
+ int halfThumb = thumbWidth / 2;
+ int paddingLeft = getPaddingLeft() + addedThumb;
+ int paddingRight = getPaddingRight();
+ int bottom = getHeight() - getPaddingBottom() - addedThumb;
+ mThumb.setBounds(paddingLeft, bottom - thumbHeight, paddingLeft + thumbWidth, bottom);
+ int trackHeight = Math.max(mTrackHeight / 2, 1);
+ mTrack.setBounds(paddingLeft + halfThumb, bottom - halfThumb - trackHeight,
+ getWidth() - halfThumb - paddingRight - addedThumb, bottom - halfThumb + trackHeight);
+ int scrubberHeight = Math.max(mScrubberHeight / 2, 2);
+ mScrubber.setBounds(paddingLeft + halfThumb, bottom - halfThumb - scrubberHeight,
+ paddingLeft + halfThumb, bottom - halfThumb + scrubberHeight);
+
+ //Update the thumb position after size changed
+ updateThumbPosFromCurrentProgress();
+ }
+
+ @Override
+ protected synchronized void onDraw(Canvas canvas) {
+ if (!isLollipopOrGreater) {
+ mRipple.draw(canvas);
+ }
+ super.onDraw(canvas);
+ mTrack.draw(canvas);
+ mScrubber.draw(canvas);
+ mThumb.draw(canvas);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ updateFromDrawableState();
+ }
+
+ private void updateFromDrawableState() {
+ int[] state = getDrawableState();
+ boolean focused = false;
+ boolean pressed = false;
+ for (int i : state) {
+ if (i == FOCUSED_STATE) {
+ focused = true;
+ } else if (i == PRESSED_STATE) {
+ pressed = true;
+ }
+ }
+ if (isEnabled() && (focused || pressed)) {
+ //We want to add a small delay here to avoid
+ //poping in/out on simple taps
+ removeCallbacks(mShowIndicatorRunnable);
+ postDelayed(mShowIndicatorRunnable, INDICATOR_DELAY_FOR_TAPS);
+ } else {
+ hideFloater();
+ }
+ mThumb.setState(state);
+ mTrack.setState(state);
+ mScrubber.setState(state);
+ mRipple.setState(state);
+ }
+
+ private void updateProgressMessage(int value) {
+ if (!isInEditMode()) {
+ if (mNumericTransformer.useStringTransform()) {
+ mIndicator.setValue(mNumericTransformer.transformToString(value));
+ } else {
+ mIndicator.setValue(convertValueToMessage(mNumericTransformer.transform(value)));
+ }
+ }
+ }
+
+ private String convertValueToMessage(int value) {
+ String format = mIndicatorFormatter != null ? mIndicatorFormatter : DEFAULT_FORMATTER;
+ //We're trying to re-use the Formatter here to avoid too much memory allocations
+ //But I'm not completey sure if it's doing anything good... :(
+ //Previously, this condition was wrong so the Formatter was always re-created
+ //But as I fixed the condition, the formatter started outputting trash characters from previous
+ //calls, so I mark the StringBuilder as empty before calling format again.
+
+ //Anyways, I see the memory usage still go up on every call to this method
+ //and I have no clue on how to fix that... damn Strings...
+ if (mFormatter == null || !mFormatter.locale().equals(Locale.getDefault())) {
+ int bufferSize = format.length() + String.valueOf(mMax).length();
+ if (mFormatBuilder == null) {
+ mFormatBuilder = new StringBuilder(bufferSize);
+ } else {
+ mFormatBuilder.ensureCapacity(bufferSize);
+ }
+ mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+ } else {
+ mFormatBuilder.setLength(0);
+ }
+ return mFormatter.format(format, value).toString();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!isEnabled()) {
+ return false;
+ }
+ int actionMasked = MotionEventCompat.getActionMasked(event);
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ mDownX = event.getX();
+ startDragging(event, isInScrollingContainer());
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (isDragging()) {
+ updateDragging(event);
+ } else {
+ final float x = event.getX();
+ if (Math.abs(x - mDownX) > mTouchSlop) {
+ startDragging(event, false);
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ stopDragging();
+ break;
+ }
+ return true;
+ }
+
+ private boolean isInScrollingContainer() {
+ return SeekBarCompat.isInScrollingContainer(getParent());
+ }
+
+ private boolean startDragging(MotionEvent ev, boolean ignoreTrackIfInScrollContainer) {
+ final Rect bounds = mTempRect;
+ mThumb.copyBounds(bounds);
+ //Grow the current thumb rect for a bigger touch area
+ bounds.inset(-mAddedTouchBounds, -mAddedTouchBounds);
+ mIsDragging = (bounds.contains((int) ev.getX(), (int) ev.getY()));
+ if (!mIsDragging && mAllowTrackClick && !ignoreTrackIfInScrollContainer) {
+ //If the user clicked outside the thumb, we compute the current position
+ //and force an immediate drag to it.
+ mIsDragging = true;
+ mDragOffset = (bounds.width() / 2) - mAddedTouchBounds;
+ updateDragging(ev);
+ //As the thumb may have moved, get the bounds again
+ mThumb.copyBounds(bounds);
+ bounds.inset(-mAddedTouchBounds, -mAddedTouchBounds);
+ }
+ if (mIsDragging) {
+ setPressed(true);
+ attemptClaimDrag();
+ setHotspot(ev.getX(), ev.getY());
+ mDragOffset = (int) (ev.getX() - bounds.left - mAddedTouchBounds);
+ if (mPublicChangeListener != null) {
+ mPublicChangeListener.onStartTrackingTouch(this);
+ }
+ }
+ return mIsDragging;
+ }
+
+ private boolean isDragging() {
+ return mIsDragging;
+ }
+
+ private void stopDragging() {
+ if (mPublicChangeListener != null) {
+ mPublicChangeListener.onStopTrackingTouch(this);
+ }
+ mIsDragging = false;
+ setPressed(false);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ //TODO: Should we reverse the keys for RTL? The framework's SeekBar does NOT....
+ boolean handled = false;
+ if (isEnabled()) {
+ int progress = getAnimatedProgress();
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ handled = true;
+ if (progress <= mMin) break;
+ animateSetProgress(progress - mKeyProgressIncrement);
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ handled = true;
+ if (progress >= mMax) break;
+ animateSetProgress(progress + mKeyProgressIncrement);
+ break;
+ }
+ }
+
+ return handled || super.onKeyDown(keyCode, event);
+ }
+
+ private int getAnimatedProgress() {
+ return isAnimationRunning() ? getAnimationTarget() : mValue;
+ }
+
+
+ boolean isAnimationRunning() {
+ return mPositionAnimator != null && mPositionAnimator.isRunning();
+ }
+
+ void animateSetProgress(int progress) {
+ final float curProgress = isAnimationRunning() ? getAnimationPosition() : getProgress();
+
+ if (progress < mMin) {
+ progress = mMin;
+ } else if (progress > mMax) {
+ progress = mMax;
+ }
+ //setProgressValueOnly(progress);
+
+ if (mPositionAnimator != null) {
+ mPositionAnimator.cancel();
+ }
+
+ mAnimationTarget = progress;
+ mPositionAnimator = AnimatorCompat.create(curProgress,
+ progress, new AnimatorCompat.AnimationFrameUpdateListener() {
+ @Override
+ public void onAnimationFrame(float currentValue) {
+ setAnimationPosition(currentValue);
+ }
+ });
+ mPositionAnimator.setDuration(PROGRESS_ANIMATION_DURATION);
+ mPositionAnimator.start();
+ }
+
+ private int getAnimationTarget() {
+ return mAnimationTarget;
+ }
+
+ void setAnimationPosition(float position) {
+ mAnimationPosition = position;
+ float currentScale = (position - mMin) / (float) (mMax - mMin);
+ updateProgressFromAnimation(currentScale);
+ }
+
+ float getAnimationPosition() {
+ return mAnimationPosition;
+ }
+
+
+ private void updateDragging(MotionEvent ev) {
+ setHotspot(ev.getX(), ev.getY());
+ int x = (int) ev.getX();
+ Rect oldBounds = mThumb.getBounds();
+ int halfThumb = oldBounds.width() / 2;
+ int addedThumb = mAddedTouchBounds;
+ int newX = x - mDragOffset + halfThumb;
+ int left = getPaddingLeft() + halfThumb + addedThumb;
+ int right = getWidth() - (getPaddingRight() + halfThumb + addedThumb);
+ if (newX < left) {
+ newX = left;
+ } else if (newX > right) {
+ newX = right;
+ }
+
+ int available = right - left;
+ float scale = (float) (newX - left) / (float) available;
+ if (isRtl()) {
+ scale = 1f - scale;
+ }
+ int progress = Math.round((scale * (mMax - mMin)) + mMin);
+ setProgress(progress, true);
+ }
+
+ private void updateProgressFromAnimation(float scale) {
+ Rect bounds = mThumb.getBounds();
+ int halfThumb = bounds.width() / 2;
+ int addedThumb = mAddedTouchBounds;
+ int left = getPaddingLeft() + halfThumb + addedThumb;
+ int right = getWidth() - (getPaddingRight() + halfThumb + addedThumb);
+ int available = right - left;
+ int progress = Math.round((scale * (mMax - mMin)) + mMin);
+ //we don't want to just call setProgress here to avoid the animation being cancelled,
+ //and this position is not bound to a real progress value but interpolated
+ if (progress != getProgress()) {
+ mValue = progress;
+ notifyProgress(mValue, true);
+ updateProgressMessage(progress);
+ }
+ final int thumbPos = (int) (scale * available + 0.5f);
+ updateThumbPos(thumbPos);
+ }
+
+ private void updateThumbPosFromCurrentProgress() {
+ int thumbWidth = mThumb.getIntrinsicWidth();
+ int addedThumb = mAddedTouchBounds;
+ int halfThumb = thumbWidth / 2;
+ float scaleDraw = (mValue - mMin) / (float) (mMax - mMin);
+
+ //This doesn't matter if RTL, as we just need the "avaiable" area
+ int left = getPaddingLeft() + halfThumb + addedThumb;
+ int right = getWidth() - (getPaddingRight() + halfThumb + addedThumb);
+ int available = right - left;
+
+ final int thumbPos = (int) (scaleDraw * available + 0.5f);
+ updateThumbPos(thumbPos);
+ }
+
+ private void updateThumbPos(int posX) {
+ int thumbWidth = mThumb.getIntrinsicWidth();
+ int halfThumb = thumbWidth / 2;
+ int start;
+ if (isRtl()) {
+ start = getWidth() - getPaddingRight() - mAddedTouchBounds;
+ posX = start - posX - thumbWidth;
+ } else {
+ start = getPaddingLeft() + mAddedTouchBounds;
+ posX = start + posX;
+ }
+ mThumb.copyBounds(mInvalidateRect);
+ mThumb.setBounds(posX, mInvalidateRect.top, posX + thumbWidth, mInvalidateRect.bottom);
+ if (isRtl()) {
+ mScrubber.getBounds().right = start - halfThumb;
+ mScrubber.getBounds().left = posX + halfThumb;
+ } else {
+ mScrubber.getBounds().left = start + halfThumb;
+ mScrubber.getBounds().right = posX + halfThumb;
+ }
+ final Rect finalBounds = mTempRect;
+ mThumb.copyBounds(finalBounds);
+ if (!isInEditMode()) {
+ mIndicator.move(finalBounds.centerX());
+ }
+
+ mInvalidateRect.inset(-mAddedTouchBounds, -mAddedTouchBounds);
+ finalBounds.inset(-mAddedTouchBounds, -mAddedTouchBounds);
+ mInvalidateRect.union(finalBounds);
+ SeekBarCompat.setHotspotBounds(mRipple, finalBounds.left, finalBounds.top, finalBounds.right, finalBounds.bottom);
+ invalidate(mInvalidateRect);
+ }
+
+
+ private void setHotspot(float x, float y) {
+ DrawableCompat.setHotspot(mRipple, x, y);
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mThumb || who == mTrack || who == mScrubber || who == mRipple || super.verifyDrawable(who);
+ }
+
+ private void attemptClaimDrag() {
+ ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+
+ private Runnable mShowIndicatorRunnable = new Runnable() {
+ @Override
+ public void run() {
+ showFloater();
+ }
+ };
+
+ private void showFloater() {
+ if (!isInEditMode()) {
+ mThumb.animateToPressed();
+ mIndicator.showIndicator(this, mThumb.getBounds());
+ notifyBubble(true);
+ }
+ }
+
+ private void hideFloater() {
+ removeCallbacks(mShowIndicatorRunnable);
+ if (!isInEditMode()) {
+ mIndicator.dismiss();
+ notifyBubble(false);
+ }
+ }
+
+ private MarkerDrawable.MarkerAnimationListener mFloaterListener = new MarkerDrawable.MarkerAnimationListener() {
+ @Override
+ public void onClosingComplete() {
+ mThumb.animateToNormal();
+ }
+
+ @Override
+ public void onOpeningComplete() {
+
+ }
+
+ };
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ removeCallbacks(mShowIndicatorRunnable);
+ if (!isInEditMode()) {
+ mIndicator.dismissComplete();
+ }
+ }
+
+ public boolean isRtl() {
+ return (ViewCompat.getLayoutDirection(this) == LAYOUT_DIRECTION_RTL) && mMirrorForRtl;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ CustomState state = new CustomState(superState);
+ state.progress = getProgress();
+ state.max = mMax;
+ state.min = mMin;
+ return state;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(CustomState.class)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ CustomState customState = (CustomState) state;
+ setMin(customState.min);
+ setMax(customState.max);
+ setProgress(customState.progress, false);
+ super.onRestoreInstanceState(customState.getSuperState());
+ }
+
+ static class CustomState extends BaseSavedState {
+ private int progress;
+ private int max;
+ private int min;
+
+ public CustomState(Parcel source) {
+ super(source);
+ progress = source.readInt();
+ max = source.readInt();
+ min = source.readInt();
+ }
+
+ public CustomState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel outcoming, int flags) {
+ super.writeToParcel(outcoming, flags);
+ outcoming.writeInt(progress);
+ outcoming.writeInt(max);
+ outcoming.writeInt(min);
+ }
+
+ public static final Creator CREATOR =
+ new Creator() {
+
+ @Override
+ public CustomState[] newArray(int size) {
+ return new CustomState[size];
+ }
+
+ @Override
+ public CustomState createFromParcel(Parcel incoming) {
+ return new CustomState(incoming);
+ }
+ };
+ }
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/Marker.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/Marker.java
new file mode 100644
index 0000000..c00be23
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/Marker.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import org.adw.library.widgets.discreteseekbar.R;
+import org.adw.library.widgets.discreteseekbar.internal.compat.SeekBarCompat;
+import org.adw.library.widgets.discreteseekbar.internal.drawable.MarkerDrawable;
+import org.adw.library.widgets.discreteseekbar.internal.drawable.ThumbDrawable;
+
+/**
+ * {@link android.view.ViewGroup} to be used as the real indicator.
+ *
+ * I've used this to be able to acomodate the TextView
+ * and the {@link org.adw.library.widgets.discreteseekbar.internal.drawable.MarkerDrawable}
+ * with the required positions and offsets
+ *
+ *
+ * @hide
+ */
+public class Marker extends ViewGroup implements MarkerDrawable.MarkerAnimationListener {
+ private static final int PADDING_DP = 4;
+ private static final int ELEVATION_DP = 8;
+ private static final int SEPARATION_DP = 30;
+ //The TextView to show the info
+ private TextView mNumber;
+ //The max width of this View
+ private int mWidth;
+ //some distance between the thumb and our bubble marker.
+ //This will be added to our measured height
+ private int mSeparation;
+ MarkerDrawable mMarkerDrawable;
+
+ public Marker(Context context) {
+ this(context, null);
+ }
+
+ public Marker(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.discreteSeekBarStyle);
+ }
+
+ public Marker(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, "0");
+ }
+
+ public Marker(Context context, AttributeSet attrs, int defStyleAttr, String maxValue) {
+ super(context, attrs, defStyleAttr);
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscreteSeekBar,
+ R.attr.discreteSeekBarStyle, R.style.DefaultSeekBar);
+
+ int padding = (int) (PADDING_DP * displayMetrics.density) * 2;
+ int textAppearanceId = a.getResourceId(R.styleable.DiscreteSeekBar_dsb_indicatorTextAppearance,
+ R.style.DefaultIndicatorTextAppearance);
+ mNumber = new TextView(context);
+ //Add some padding to this textView so the bubble has some space to breath
+ mNumber.setPadding(padding, 0, padding, 0);
+ mNumber.setTextAppearance(context, textAppearanceId);
+ mNumber.setGravity(Gravity.CENTER);
+ mNumber.setText(maxValue);
+ mNumber.setMaxLines(1);
+ mNumber.setSingleLine(true);
+ SeekBarCompat.setTextDirection(mNumber, TEXT_DIRECTION_LOCALE);
+ mNumber.setVisibility(View.INVISIBLE);
+
+ //add some padding for the elevation shadow not to be clipped
+ //I'm sure there are better ways of doing this...
+ setPadding(padding, padding, padding, padding);
+
+ resetSizes(maxValue);
+
+ mSeparation = (int) (SEPARATION_DP * displayMetrics.density);
+ int thumbSize = (int) (ThumbDrawable.DEFAULT_SIZE_DP * displayMetrics.density);
+ ColorStateList color = a.getColorStateList(R.styleable.DiscreteSeekBar_dsb_indicatorColor);
+ mMarkerDrawable = new MarkerDrawable(color, thumbSize);
+ mMarkerDrawable.setCallback(this);
+ mMarkerDrawable.setMarkerListener(this);
+ mMarkerDrawable.setExternalOffset(padding);
+
+ //Elevation for anroid 5+
+ float elevation = a.getDimension(R.styleable.DiscreteSeekBar_dsb_indicatorElevation, ELEVATION_DP * displayMetrics.density);
+ ViewCompat.setElevation(this, elevation);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ SeekBarCompat.setOutlineProvider(this, mMarkerDrawable);
+ }
+ a.recycle();
+ }
+
+ public void resetSizes(String maxValue) {
+ DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ //Account for negative numbers... is there any proper way of getting the biggest string between our range????
+ mNumber.setText("-" + maxValue);
+ //Do a first forced measure call for the TextView (with the biggest text content),
+ //to calculate the max width and use always the same.
+ //this avoids the TextView from shrinking and growing when the text content changes
+ int wSpec = MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, MeasureSpec.AT_MOST);
+ int hSpec = MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels, MeasureSpec.AT_MOST);
+ mNumber.measure(wSpec, hSpec);
+ mWidth = Math.max(mNumber.getMeasuredWidth(), mNumber.getMeasuredHeight());
+ removeView(mNumber);
+ addView(mNumber, new FrameLayout.LayoutParams(mWidth, mWidth, Gravity.LEFT | Gravity.TOP));
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ mMarkerDrawable.draw(canvas);
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ int widthSize = mWidth + getPaddingLeft() + getPaddingRight();
+ int heightSize = mWidth + getPaddingTop() + getPaddingBottom();
+ //This diff is the basic calculation of the difference between
+ //a square side size and its diagonal
+ //this helps us account for the visual offset created by MarkerDrawable
+ //when leaving one of the corners un-rounded
+ int diff = (int) ((1.41f * mWidth) - mWidth) / 2;
+ setMeasuredDimension(widthSize, heightSize + diff + mSeparation);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int left = getPaddingLeft();
+ int top = getPaddingTop();
+ int right = getWidth() - getPaddingRight();
+ int bottom = getHeight() - getPaddingBottom();
+ //the TetView is always layout at the top
+ mNumber.layout(left, top, left + mWidth, top + mWidth);
+ //the MarkerDrawable uses the whole view, it will adapt itself...
+ // or it seems so...
+ mMarkerDrawable.setBounds(left, top, right, bottom);
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mMarkerDrawable || super.verifyDrawable(who);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ //HACK: Sometimes, the animateOpen() call is made before the View is attached
+ //so the drawable cannot schedule itself to run the animation
+ //I think we can call it here safely.
+ //I've seen it happen in android 2.3.7
+ animateOpen();
+ }
+
+ public void setValue(CharSequence value) {
+ mNumber.setText(value);
+ }
+
+ public CharSequence getValue() {
+ return mNumber.getText();
+ }
+
+ public void animateOpen() {
+ mMarkerDrawable.stop();
+ mMarkerDrawable.animateToPressed();
+ }
+
+ public void animateClose() {
+ mMarkerDrawable.stop();
+ ViewCompat.animate(mNumber)
+ .alpha(0f)
+ .setDuration(100)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ //We use INVISIBLE instead of GONE to avoid a requestLayout
+ mNumber.setVisibility(View.INVISIBLE);
+ mMarkerDrawable.animateToNormal();
+ }
+ }).start();
+ }
+
+ @Override
+ public void onOpeningComplete() {
+ mNumber.setVisibility(View.VISIBLE);
+ ViewCompat.animate(mNumber)
+ .alpha(1f)
+ .setDuration(100)
+ .start();
+ if (getParent() instanceof MarkerDrawable.MarkerAnimationListener) {
+ ((MarkerDrawable.MarkerAnimationListener) getParent()).onOpeningComplete();
+ }
+ }
+
+ @Override
+ public void onClosingComplete() {
+ if (getParent() instanceof MarkerDrawable.MarkerAnimationListener) {
+ ((MarkerDrawable.MarkerAnimationListener) getParent()).onClosingComplete();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mMarkerDrawable.stop();
+ }
+
+ public void setColors(int startColor, int endColor) {
+ mMarkerDrawable.setColors(startColor, endColor);
+ }
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/PopupIndicator.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/PopupIndicator.java
new file mode 100644
index 0000000..8690dd4
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/PopupIndicator.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.IBinder;
+import android.support.v4.view.GravityCompat;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import org.adw.library.widgets.discreteseekbar.internal.compat.SeekBarCompat;
+import org.adw.library.widgets.discreteseekbar.internal.drawable.MarkerDrawable;
+
+/**
+ * Class to manage the floating bubble thing, similar (but quite worse tested than {@link android.widget.PopupWindow}
+ *
+ *
+ * This will attach a View to the Window (full-width, measured-height, positioned just under the thumb)
+ *
+ *
+ * @hide
+ * @see #showIndicator(android.view.View, android.graphics.Rect)
+ * @see #dismiss()
+ * @see #dismissComplete()
+ * @see org.adw.library.widgets.discreteseekbar.internal.PopupIndicator.Floater
+ */
+public class PopupIndicator {
+
+ private final WindowManager mWindowManager;
+ private boolean mShowing;
+ private Floater mPopupView;
+ //Outside listener for the DiscreteSeekBar to get MarkerDrawable animation events.
+ //The whole chain of events goes this way:
+ //MarkerDrawable->Marker->Floater->mListener->DiscreteSeekBar....
+ //... phew!
+ private MarkerDrawable.MarkerAnimationListener mListener;
+ private int[] mDrawingLocation = new int[2];
+ Point screenSize = new Point();
+
+ public PopupIndicator(Context context, AttributeSet attrs, int defStyleAttr, String maxValue) {
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mPopupView = new Floater(context, attrs, defStyleAttr, maxValue);
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ screenSize.set(displayMetrics.widthPixels, displayMetrics.heightPixels);
+ }
+
+ public void updateSizes(String maxValue) {
+ dismissComplete();
+ if (mPopupView != null) {
+ mPopupView.mMarker.resetSizes(maxValue);
+ }
+ }
+
+ public void setListener(MarkerDrawable.MarkerAnimationListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * We want the Floater to be full-width because the contents will be moved from side to side.
+ * We may/should change this in the future to use just the PARENT View width and/or pass it in the constructor
+ */
+ private void measureFloater() {
+ int specWidth = View.MeasureSpec.makeMeasureSpec(screenSize.x, View.MeasureSpec.EXACTLY);
+ int specHeight = View.MeasureSpec.makeMeasureSpec(screenSize.y, View.MeasureSpec.AT_MOST);
+ mPopupView.measure(specWidth, specHeight);
+ }
+
+ public void setValue(CharSequence value) {
+ mPopupView.mMarker.setValue(value);
+ }
+
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ public void showIndicator(View parent, Rect touchBounds) {
+ if (isShowing()) {
+ mPopupView.mMarker.animateOpen();
+ return;
+ }
+
+ IBinder windowToken = parent.getWindowToken();
+ if (windowToken != null) {
+ WindowManager.LayoutParams p = createPopupLayout(windowToken);
+
+ p.gravity = Gravity.TOP | GravityCompat.START;
+ updateLayoutParamsForPosiion(parent, p, touchBounds.bottom);
+ mShowing = true;
+
+ translateViewIntoPosition(touchBounds.centerX());
+ invokePopup(p);
+ }
+ }
+
+ public void move(int x) {
+ if (!isShowing()) {
+ return;
+ }
+ translateViewIntoPosition(x);
+ }
+
+ public void setColors(int startColor, int endColor) {
+ mPopupView.setColors(startColor, endColor);
+ }
+
+ /**
+ * This will start the closing animation of the Marker and call onClosingComplete when finished
+ */
+ public void dismiss() {
+ mPopupView.mMarker.animateClose();
+ }
+
+ /**
+ * FORCE the popup window to be removed.
+ * You typically calls this when the parent view is being removed from the window to avoid a Window Leak
+ */
+ public void dismissComplete() {
+ if (isShowing()) {
+ mShowing = false;
+ try {
+ mWindowManager.removeViewImmediate(mPopupView);
+ } finally {
+ }
+ }
+ }
+
+ private void updateLayoutParamsForPosiion(View anchor, WindowManager.LayoutParams p, int yOffset) {
+ measureFloater();
+ int measuredHeight = mPopupView.getMeasuredHeight();
+ int paddingBottom = mPopupView.mMarker.getPaddingBottom();
+ anchor.getLocationInWindow(mDrawingLocation);
+ p.x = 0;
+ p.y = mDrawingLocation[1] - measuredHeight + yOffset + paddingBottom;
+ p.width = screenSize.x;
+ p.height = measuredHeight;
+ }
+
+ private void translateViewIntoPosition(final int x) {
+ mPopupView.setFloatOffset(x + mDrawingLocation[0]);
+ }
+
+ private void invokePopup(WindowManager.LayoutParams p) {
+ mWindowManager.addView(mPopupView, p);
+ mPopupView.mMarker.animateOpen();
+ }
+
+ private WindowManager.LayoutParams createPopupLayout(IBinder token) {
+ WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+ p.gravity = Gravity.START | Gravity.TOP;
+ p.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ p.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ p.format = PixelFormat.TRANSLUCENT;
+ p.flags = computeFlags(p.flags);
+ p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ p.token = token;
+ p.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+ p.setTitle("DiscreteSeekBar Indicator:" + Integer.toHexString(hashCode()));
+
+ return p;
+ }
+
+ /**
+ * I'm NOT completely sure how all this bitwise things work...
+ *
+ * @param curFlags
+ * @return
+ */
+ private int computeFlags(int curFlags) {
+ curFlags &= ~(
+ WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
+ curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ return curFlags;
+ }
+
+ /**
+ * Small FrameLayout class to hold and move the bubble around when requested
+ * I wanted to use the {@link Marker} directly
+ * but doing so would make some things harder to implement
+ * (like moving the marker around, having the Marker's outline to work, etc)
+ */
+ private class Floater extends FrameLayout implements MarkerDrawable.MarkerAnimationListener {
+ private Marker mMarker;
+ private int mOffset;
+
+ public Floater(Context context, AttributeSet attrs, int defStyleAttr, String maxValue) {
+ super(context);
+ mMarker = new Marker(context, attrs, defStyleAttr, maxValue);
+ addView(mMarker, new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP));
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSie = mMarker.getMeasuredHeight();
+ setMeasuredDimension(widthSize, heightSie);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int centerDiffX = mMarker.getMeasuredWidth() / 2;
+ int offset = (mOffset - centerDiffX);
+ mMarker.layout(offset, 0, offset + mMarker.getMeasuredWidth(), mMarker.getMeasuredHeight());
+ }
+
+ public void setFloatOffset(int x) {
+ mOffset = x;
+ int centerDiffX = mMarker.getMeasuredWidth() / 2;
+ int offset = (x - centerDiffX);
+ mMarker.offsetLeftAndRight(offset - mMarker.getLeft());
+ //Without hardware acceleration (or API levels<11), offsetting a view seems to NOT invalidate the proper area.
+ //We should calc the proper invalidate Rect but this will be for now...
+ if (!SeekBarCompat.isHardwareAccelerated(this)) {
+ invalidate();
+ }
+ }
+
+ @Override
+ public void onClosingComplete() {
+ if (mListener != null) {
+ mListener.onClosingComplete();
+ }
+ dismissComplete();
+ }
+
+ @Override
+ public void onOpeningComplete() {
+ if (mListener != null) {
+ mListener.onOpeningComplete();
+ }
+ }
+
+ public void setColors(int startColor, int endColor) {
+ mMarker.setColors(startColor, endColor);
+ }
+ }
+
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/AnimatorCompat.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/AnimatorCompat.java
new file mode 100644
index 0000000..1a13552
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/AnimatorCompat.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.compat;
+
+import android.os.Build;
+
+/**
+ * Currently, there's no {@link android.animation.ValueAnimator} compatibility version
+ * and as we didn't want to throw in external dependencies, we made this small class.
+ *
+ *
+ * This will work like {@link android.support.v4.view.ViewPropertyAnimatorCompat}, that is,
+ * not doing anything on API<11 and using the default {@link android.animation.ValueAnimator}
+ * on API>=11
+ *
+ *
+ * This class is used to provide animation to the {@link org.adw.library.widgets.discreteseekbar.DiscreteSeekBar}
+ * when navigating with the Keypad
+ *
+ *
+ * @hide
+ */
+public abstract class AnimatorCompat {
+ public interface AnimationFrameUpdateListener {
+ public void onAnimationFrame(float currentValue);
+ }
+
+ AnimatorCompat() {
+
+ }
+
+ public abstract void cancel();
+
+ public abstract boolean isRunning();
+
+ public abstract void setDuration(int progressAnimationDuration);
+
+ public abstract void start();
+
+ public static final AnimatorCompat create(float start, float end, AnimationFrameUpdateListener listener) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ return new AnimatorCompatV11(start, end, listener);
+ } else {
+ return new AnimatorCompatBase(start, end, listener);
+ }
+ }
+
+ private static class AnimatorCompatBase extends AnimatorCompat {
+
+ private final AnimationFrameUpdateListener mListener;
+ private final float mEndValue;
+
+ public AnimatorCompatBase(float start, float end, AnimationFrameUpdateListener listener) {
+ mListener = listener;
+ mEndValue = end;
+ }
+
+ @Override
+ public void cancel() {
+
+ }
+
+ @Override
+ public boolean isRunning() {
+ return false;
+ }
+
+ @Override
+ public void setDuration(int progressAnimationDuration) {
+
+ }
+
+ @Override
+ public void start() {
+ mListener.onAnimationFrame(mEndValue);
+ }
+ }
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/AnimatorCompatV11.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/AnimatorCompatV11.java
new file mode 100644
index 0000000..a2aa27e
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/AnimatorCompatV11.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.compat;
+
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
+
+/**
+ * Class to wrap a {@link android.animation.ValueAnimator}
+ * for use with AnimatorCompat
+ *
+ * @hide
+ * @see {@link org.adw.library.widgets.discreteseekbar.internal.compat.AnimatorCompat}
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+public class AnimatorCompatV11 extends AnimatorCompat {
+
+ ValueAnimator animator;
+
+ public AnimatorCompatV11(float start, float end, final AnimationFrameUpdateListener listener) {
+ super();
+ animator = ValueAnimator.ofFloat(start, end);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ listener.onAnimationFrame((Float) animation.getAnimatedValue());
+ }
+ });
+ }
+
+ @Override
+ public void cancel() {
+ animator.cancel();
+ }
+
+ @Override
+ public boolean isRunning() {
+ return animator.isRunning();
+ }
+
+ @Override
+ public void setDuration(int duration) {
+ animator.setDuration(duration);
+ }
+
+ @Override
+ public void start() {
+ animator.start();
+ }
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/SeekBarCompat.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/SeekBarCompat.java
new file mode 100644
index 0000000..cd3583d
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/SeekBarCompat.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.compat;
+
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.TextView;
+
+import org.adw.library.widgets.discreteseekbar.internal.drawable.AlmostRippleDrawable;
+import org.adw.library.widgets.discreteseekbar.internal.drawable.MarkerDrawable;
+
+/**
+ * Wrapper compatibility class to call some API-Specific methods
+ * And offer alternate procedures when possible
+ *
+ * @hide
+ */
+public class SeekBarCompat {
+
+ /**
+ * Sets the custom Outline provider on API>=21.
+ * Does nothing on API<21
+ *
+ * @param view
+ * @param markerDrawable
+ */
+ public static void setOutlineProvider(View view, final MarkerDrawable markerDrawable) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ SeekBarCompatDontCrash.setOutlineProvider(view, markerDrawable);
+ }
+ }
+
+ /**
+ * Our DiscreteSeekBar implementation uses a circular drawable on API < 21
+ * because we don't set it as Background, but draw it ourselves
+ *
+ * @param colorStateList
+ * @return
+ */
+ public static Drawable getRipple(ColorStateList colorStateList) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return SeekBarCompatDontCrash.getRipple(colorStateList);
+ } else {
+ return new AlmostRippleDrawable(colorStateList);
+ }
+ }
+
+ /**
+ * As our DiscreteSeekBar implementation uses a circular drawable on API < 21
+ * we want to use the same method to set its bounds as the Ripple's hotspot bounds.
+ *
+ * @param drawable
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ */
+ public static void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ //We don't want the full size rect, Lollipop ripple would be too big
+ int size = (right - left) / 8;
+ DrawableCompat.setHotspotBounds(drawable, left + size, top + size, right - size, bottom - size);
+ } else {
+ drawable.setBounds(left, top, right, bottom);
+ }
+ }
+
+ /**
+ * android.support.v4.view.ViewCompat SHOULD include this once and for all!!
+ * But it doesn't...
+ *
+ * @param view
+ * @param background
+ */
+ @SuppressWarnings("deprecation")
+ public static void setBackground(View view, Drawable background) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ SeekBarCompatDontCrash.setBackground(view, background);
+ } else {
+ view.setBackgroundDrawable(background);
+ }
+ }
+
+ /**
+ * Sets the TextView text direction attribute when possible
+ *
+ * @param textView
+ * @param textDirection
+ * @see android.widget.TextView#setTextDirection(int)
+ */
+ public static void setTextDirection(TextView textView, int textDirection) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ SeekBarCompatDontCrash.setTextDirection(textView, textDirection);
+ }
+ }
+
+ public static boolean isInScrollingContainer(ViewParent p) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return SeekBarCompatDontCrash.isInScrollingContainer(p);
+ }
+ return false;
+ }
+
+ public static boolean isHardwareAccelerated(View view) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ return SeekBarCompatDontCrash.isHardwareAccelerated(view);
+ }
+ return false;
+ }
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/SeekBarCompatDontCrash.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/SeekBarCompatDontCrash.java
new file mode 100644
index 0000000..f143427
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/compat/SeekBarCompatDontCrash.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.compat;
+
+import android.annotation.TargetApi;
+import android.content.res.ColorStateList;
+import android.graphics.Outline;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RippleDrawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.ViewParent;
+import android.widget.TextView;
+
+import org.adw.library.widgets.discreteseekbar.internal.drawable.MarkerDrawable;
+
+/**
+ * Wrapper compatibility class to call some API-Specific methods
+ * And offer alternate procedures when possible
+ *
+ * @hide
+ */
+@TargetApi(21)
+class SeekBarCompatDontCrash {
+ public static void setOutlineProvider(View marker, final MarkerDrawable markerDrawable) {
+ marker.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setConvexPath(markerDrawable.getPath());
+ }
+ });
+ }
+
+ public static Drawable getRipple(ColorStateList colorStateList) {
+ return new RippleDrawable(colorStateList, null, null);
+ }
+
+ public static void setBackground(View view, Drawable background) {
+ view.setBackground(background);
+ }
+
+ public static void setTextDirection(TextView number, int textDirection) {
+ number.setTextDirection(textDirection);
+ }
+
+ public static boolean isInScrollingContainer(ViewParent p) {
+ while (p != null && p instanceof ViewGroup) {
+ if (((ViewGroup) p).shouldDelayChildPressedState()) {
+ return true;
+ }
+ p = p.getParent();
+ }
+ return false;
+ }
+
+ public static boolean isHardwareAccelerated(View view) {
+ return view.isHardwareAccelerated();
+ }
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/AlmostRippleDrawable.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/AlmostRippleDrawable.java
new file mode 100644
index 0000000..3566d92
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/AlmostRippleDrawable.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+public class AlmostRippleDrawable extends StateDrawable implements Animatable {
+ private static final long FRAME_DURATION = 1000 / 60;
+ private static final int ANIMATION_DURATION = 250;
+
+ private static final float INACTIVE_SCALE = 0f;
+ private static final float ACTIVE_SCALE = 1f;
+ private float mCurrentScale = INACTIVE_SCALE;
+ private Interpolator mInterpolator;
+ private long mStartTime;
+ private boolean mReverse = false;
+ private boolean mRunning = false;
+ private int mDuration = ANIMATION_DURATION;
+ private float mAnimationInitialValue;
+ //We don't use colors just with our drawable state because of animations
+ private int mPressedColor;
+ private int mFocusedColor;
+ private int mDisabledColor;
+ private int mRippleColor;
+ private int mRippleBgColor;
+
+ public AlmostRippleDrawable(@NonNull ColorStateList tintStateList) {
+ super(tintStateList);
+ mInterpolator = new AccelerateDecelerateInterpolator();
+ mFocusedColor = tintStateList.getColorForState(new int[]{android.R.attr.state_focused}, 0xFFFF0000);
+ mPressedColor = tintStateList.getColorForState(new int[]{android.R.attr.state_pressed}, 0xFFFF0000);
+ mDisabledColor = tintStateList.getColorForState(new int[]{-android.R.attr.state_enabled}, 0xFFFF0000);
+ }
+
+ @Override
+ public void doDraw(Canvas canvas, Paint paint) {
+ Rect bounds = getBounds();
+ int size = Math.min(bounds.width(), bounds.height());
+ float scale = mCurrentScale;
+ int rippleColor = mRippleColor;
+ int bgColor = mRippleBgColor;
+ float radius = (size / 2);
+ float radiusAnimated = radius * scale;
+ if (scale > INACTIVE_SCALE) {
+ if (bgColor != 0) {
+ paint.setColor(bgColor);
+ paint.setAlpha(decreasedAlpha(Color.alpha(bgColor)));
+ canvas.drawCircle(bounds.centerX(), bounds.centerY(), radius, paint);
+ }
+ if (rippleColor != 0) {
+ paint.setColor(rippleColor);
+ paint.setAlpha(modulateAlpha(Color.alpha(rippleColor)));
+ canvas.drawCircle(bounds.centerX(), bounds.centerY(), radiusAnimated, paint);
+ }
+ }
+ }
+
+ private int decreasedAlpha(int alpha) {
+ int scale = 100 + (100 >> 7);
+ return alpha * scale >> 8;
+ }
+
+ @Override
+ public boolean setState(int[] stateSet) {
+ int[] oldState = getState();
+ boolean oldPressed = false;
+ for (int i : oldState) {
+ if (i == android.R.attr.state_pressed) {
+ oldPressed = true;
+ }
+ }
+ super.setState(stateSet);
+ boolean focused = false;
+ boolean pressed = false;
+ boolean disabled = true;
+ for (int i : stateSet) {
+ if (i == android.R.attr.state_focused) {
+ focused = true;
+ } else if (i == android.R.attr.state_pressed) {
+ pressed = true;
+ } else if (i == android.R.attr.state_enabled) {
+ disabled = false;
+ }
+ }
+
+ if (disabled) {
+ unscheduleSelf(mUpdater);
+ mRippleColor = mDisabledColor;
+ mRippleBgColor = 0;
+ mCurrentScale = ACTIVE_SCALE / 2;
+ invalidateSelf();
+ } else {
+ if (pressed) {
+ animateToPressed();
+ mRippleColor = mRippleBgColor = mPressedColor;
+ } else if (oldPressed) {
+ mRippleColor = mRippleBgColor = mPressedColor;
+ animateToNormal();
+ } else if (focused) {
+ mRippleColor = mFocusedColor;
+ mRippleBgColor = 0;
+ mCurrentScale = ACTIVE_SCALE;
+ invalidateSelf();
+ } else {
+ mRippleColor = 0;
+ mRippleBgColor = 0;
+ mCurrentScale = INACTIVE_SCALE;
+ invalidateSelf();
+ }
+ }
+ return true;
+ }
+
+ public void animateToPressed() {
+ unscheduleSelf(mUpdater);
+ if (mCurrentScale < ACTIVE_SCALE) {
+ mReverse = false;
+ mRunning = true;
+ mAnimationInitialValue = mCurrentScale;
+ float durationFactor = 1f - ((mAnimationInitialValue - INACTIVE_SCALE) / (ACTIVE_SCALE - INACTIVE_SCALE));
+ mDuration = (int) (ANIMATION_DURATION * durationFactor);
+ mStartTime = SystemClock.uptimeMillis();
+ scheduleSelf(mUpdater, mStartTime + FRAME_DURATION);
+ }
+ }
+
+ public void animateToNormal() {
+ unscheduleSelf(mUpdater);
+ if (mCurrentScale > INACTIVE_SCALE) {
+ mReverse = true;
+ mRunning = true;
+ mAnimationInitialValue = mCurrentScale;
+ float durationFactor = 1f - ((mAnimationInitialValue - ACTIVE_SCALE) / (INACTIVE_SCALE - ACTIVE_SCALE));
+ mDuration = (int) (ANIMATION_DURATION * durationFactor);
+ mStartTime = SystemClock.uptimeMillis();
+ scheduleSelf(mUpdater, mStartTime + FRAME_DURATION);
+ }
+ }
+
+ private void updateAnimation(float factor) {
+ float initial = mAnimationInitialValue;
+ float destination = mReverse ? INACTIVE_SCALE : ACTIVE_SCALE;
+ mCurrentScale = initial + (destination - initial) * factor;
+ invalidateSelf();
+ }
+
+ private final Runnable mUpdater = new Runnable() {
+
+ @Override
+ public void run() {
+
+ long currentTime = SystemClock.uptimeMillis();
+ long diff = currentTime - mStartTime;
+ if (diff < mDuration) {
+ float interpolation = mInterpolator.getInterpolation((float) diff / (float) mDuration);
+ scheduleSelf(mUpdater, currentTime + FRAME_DURATION);
+ updateAnimation(interpolation);
+ } else {
+ unscheduleSelf(mUpdater);
+ mRunning = false;
+ updateAnimation(1f);
+ }
+ }
+ };
+
+ @Override
+ public void start() {
+ //No-Op. We control our own animation
+ }
+
+ @Override
+ public void stop() {
+ //No-Op. We control our own animation
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/MarkerDrawable.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/MarkerDrawable.java
new file mode 100644
index 0000000..0f639dd
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/MarkerDrawable.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Animatable;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * Implementation of {@link StateDrawable} to draw a morphing marker symbol.
+ *
+ * It's basically an implementation of an {@link android.graphics.drawable.Animatable} Drawable with the following details:
+ *
+ *
+ *
Animates from a circle shape to a "marker" shape just using a RoundRect
+ *
Animates color change from the normal state color to the pressed state color
+ *
Uses a {@link android.graphics.Path} to also serve as Outline for API>=21
+ *
+ *
+ * @hide
+ */
+public class MarkerDrawable extends StateDrawable implements Animatable {
+ private static final long FRAME_DURATION = 1000 / 60;
+ private static final int ANIMATION_DURATION = 250;
+
+ private float mCurrentScale = 0f;
+ private Interpolator mInterpolator;
+ private long mStartTime;
+ private boolean mReverse = false;
+ private boolean mRunning = false;
+ private int mDuration = ANIMATION_DURATION;
+ //size of the actual thumb drawable to use as circle state size
+ private float mClosedStateSize;
+ //value to store que current scale when starting an animation and interpolate from it
+ private float mAnimationInitialValue;
+ //extra offset directed from the View to account
+ //for its internal padding between circle state and marker state
+ private int mExternalOffset;
+ //colors for interpolation
+ private int mStartColor;
+ private int mEndColor;
+
+ Path mPath = new Path();
+ RectF mRect = new RectF();
+ Matrix mMatrix = new Matrix();
+ private MarkerAnimationListener mMarkerListener;
+
+ public MarkerDrawable(@NonNull ColorStateList tintList, int closedSize) {
+ super(tintList);
+ mInterpolator = new AccelerateDecelerateInterpolator();
+ mClosedStateSize = closedSize;
+ mStartColor = tintList.getColorForState(new int[]{android.R.attr.state_pressed}, tintList.getDefaultColor());
+ mEndColor = tintList.getDefaultColor();
+
+ }
+
+ public void setExternalOffset(int offset) {
+ mExternalOffset = offset;
+ }
+
+ /**
+ * The two colors that will be used for the seek thumb.
+ *
+ * @param startColor Color used for the seek thumb
+ * @param endColor Color used for popup indicator
+ */
+ public void setColors(int startColor, int endColor) {
+ mStartColor = startColor;
+ mEndColor = endColor;
+ }
+
+ @Override
+ void doDraw(Canvas canvas, Paint paint) {
+ if (!mPath.isEmpty()) {
+ paint.setStyle(Paint.Style.FILL);
+ int color = blendColors(mStartColor, mEndColor, mCurrentScale);
+ paint.setColor(color);
+ canvas.drawPath(mPath, paint);
+ }
+ }
+
+ public Path getPath() {
+ return mPath;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ computePath(bounds);
+ }
+
+ private void computePath(Rect bounds) {
+ final float currentScale = mCurrentScale;
+ final Path path = mPath;
+ final RectF rect = mRect;
+ final Matrix matrix = mMatrix;
+
+ path.reset();
+ int totalSize = Math.min(bounds.width(), bounds.height());
+
+ float initial = mClosedStateSize;
+ float destination = totalSize;
+ float currentSize = initial + (destination - initial) * currentScale;
+
+ float halfSize = currentSize / 2f;
+ float inverseScale = 1f - currentScale;
+ float cornerSize = halfSize * inverseScale;
+ float[] corners = new float[]{halfSize, halfSize, halfSize, halfSize, halfSize, halfSize, cornerSize, cornerSize};
+ rect.set(bounds.left, bounds.top, bounds.left + currentSize, bounds.top + currentSize);
+ path.addRoundRect(rect, corners, Path.Direction.CCW);
+ matrix.reset();
+ matrix.postRotate(-45, bounds.left + halfSize, bounds.top + halfSize);
+ matrix.postTranslate((bounds.width() - currentSize) / 2, 0);
+ float hDiff = (bounds.bottom - currentSize - mExternalOffset) * inverseScale;
+ matrix.postTranslate(0, hDiff);
+ path.transform(matrix);
+ }
+
+ private void updateAnimation(float factor) {
+ float initial = mAnimationInitialValue;
+ float destination = mReverse ? 0f : 1f;
+ mCurrentScale = initial + (destination - initial) * factor;
+ computePath(getBounds());
+ invalidateSelf();
+ }
+
+ public void animateToPressed() {
+ unscheduleSelf(mUpdater);
+ mReverse = false;
+ if (mCurrentScale < 1) {
+ mRunning = true;
+ mAnimationInitialValue = mCurrentScale;
+ float durationFactor = 1f - mCurrentScale;
+ mDuration = (int) (ANIMATION_DURATION * durationFactor);
+ mStartTime = SystemClock.uptimeMillis();
+ scheduleSelf(mUpdater, mStartTime + FRAME_DURATION);
+ } else {
+ notifyFinishedToListener();
+ }
+ }
+
+ public void animateToNormal() {
+ mReverse = true;
+ unscheduleSelf(mUpdater);
+ if (mCurrentScale > 0) {
+ mRunning = true;
+ mAnimationInitialValue = mCurrentScale;
+ float durationFactor = 1f - mCurrentScale;
+ mDuration = ANIMATION_DURATION - (int) (ANIMATION_DURATION * durationFactor);
+ mStartTime = SystemClock.uptimeMillis();
+ scheduleSelf(mUpdater, mStartTime + FRAME_DURATION);
+ } else {
+ notifyFinishedToListener();
+ }
+ }
+
+ private final Runnable mUpdater = new Runnable() {
+
+ @Override
+ public void run() {
+
+ long currentTime = SystemClock.uptimeMillis();
+ long diff = currentTime - mStartTime;
+ if (diff < mDuration) {
+ float interpolation = mInterpolator.getInterpolation((float) diff / (float) mDuration);
+ scheduleSelf(mUpdater, currentTime + FRAME_DURATION);
+ updateAnimation(interpolation);
+ } else {
+ unscheduleSelf(mUpdater);
+ mRunning = false;
+ updateAnimation(1f);
+ notifyFinishedToListener();
+ }
+ }
+ };
+
+ public void setMarkerListener(MarkerAnimationListener listener) {
+ mMarkerListener = listener;
+ }
+
+ private void notifyFinishedToListener() {
+ if (mMarkerListener != null) {
+ if (mReverse) {
+ mMarkerListener.onClosingComplete();
+ } else {
+ mMarkerListener.onOpeningComplete();
+ }
+ }
+ }
+
+ @Override
+ public void start() {
+ //No-Op. We control our own animation
+ }
+
+ @Override
+ public void stop() {
+ unscheduleSelf(mUpdater);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ private static int blendColors(int color1, int color2, float factor) {
+ final float inverseFactor = 1f - factor;
+ float a = (Color.alpha(color1) * factor) + (Color.alpha(color2) * inverseFactor);
+ float r = (Color.red(color1) * factor) + (Color.red(color2) * inverseFactor);
+ float g = (Color.green(color1) * factor) + (Color.green(color2) * inverseFactor);
+ float b = (Color.blue(color1) * factor) + (Color.blue(color2) * inverseFactor);
+ return Color.argb((int) a, (int) r, (int) g, (int) b);
+ }
+
+
+ /**
+ * A listener interface to porpagate animation events
+ * This is the "poor's man" AnimatorListener for this Drawable
+ */
+ public interface MarkerAnimationListener {
+ public void onClosingComplete();
+
+ public void onOpeningComplete();
+ }
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/StateDrawable.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/StateDrawable.java
new file mode 100644
index 0000000..b7a02d9
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/StateDrawable.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+
+/**
+ * A drawable that changes it's Paint color depending on the Drawable State
+ *
+ * Subclasses should implement {@link #doDraw(android.graphics.Canvas, android.graphics.Paint)}
+ *
+ *
+ * @hide
+ */
+public abstract class StateDrawable extends Drawable {
+ private ColorStateList mTintStateList;
+ private final Paint mPaint;
+ private int mCurrentColor;
+ private int mAlpha = 255;
+
+ public StateDrawable(@NonNull ColorStateList tintStateList) {
+ super();
+ mTintStateList = tintStateList;
+ mCurrentColor = tintStateList.getDefaultColor();
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ }
+
+ @Override
+ public boolean isStateful() {
+ return (mTintStateList.isStateful()) || super.isStateful();
+ }
+
+ @Override
+ public boolean setState(int[] stateSet) {
+ boolean handled = super.setState(stateSet);
+ handled = updateTint(stateSet) || handled;
+ return handled;
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ private boolean updateTint(int[] state) {
+ final int color = mTintStateList.getColorForState(state, mCurrentColor);
+ if (color != mCurrentColor) {
+ mCurrentColor = color;
+ //We've changed states
+ invalidateSelf();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ mPaint.setColor(mCurrentColor);
+ int alpha = modulateAlpha(Color.alpha(mCurrentColor));
+ mPaint.setAlpha(alpha);
+ doDraw(canvas, mPaint);
+ }
+
+ public void setColorStateList(@NonNull ColorStateList tintStateList) {
+ mTintStateList = tintStateList;
+ }
+
+ /**
+ * Subclasses should implement this method to do the actual drawing
+ *
+ * @param canvas The current {@link android.graphics.Canvas} to draw into
+ * @param paint The {@link android.graphics.Paint} preconfigurred with the current
+ * {@link android.content.res.ColorStateList} color
+ */
+ abstract void doDraw(Canvas canvas, Paint paint);
+
+ @Override
+ public void setAlpha(int alpha) {
+ mAlpha = alpha;
+ invalidateSelf();
+ }
+
+ int modulateAlpha(int alpha) {
+ int scale = mAlpha + (mAlpha >> 7);
+ return alpha * scale >> 8;
+ }
+
+ @Override
+ public int getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mPaint.setColorFilter(cf);
+ }
+
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/ThumbDrawable.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/ThumbDrawable.java
new file mode 100644
index 0000000..5922323
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/ThumbDrawable.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+
+/**
+ *
HACK
+ *
+ * Special {@link org.adw.library.widgets.discreteseekbar.internal.drawable.StateDrawable} implementation
+ * to draw the Thumb circle.
+ *
+ *
+ * It's special because it will stop drawing once the state is pressed/focused BUT only after a small delay.
+ *
+ *
+ * This special delay is meant to help avoiding frame glitches while the {@link org.adw.library.widgets.discreteseekbar.internal.Marker} is added to the Window
+ *
+ *
+ * @hide
+ */
+public class ThumbDrawable extends StateDrawable implements Animatable {
+ //The current size for this drawable. Must be converted to real DPs
+ public static final int DEFAULT_SIZE_DP = 12;
+ private final int mSize;
+ private boolean mOpen;
+ private boolean mRunning;
+
+ public ThumbDrawable(@NonNull ColorStateList tintStateList, int size) {
+ super(tintStateList);
+ mSize = size;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mSize;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mSize;
+ }
+
+ @Override
+ public void doDraw(Canvas canvas, Paint paint) {
+ if (!mOpen) {
+ Rect bounds = getBounds();
+ float radius = (mSize / 2);
+ canvas.drawCircle(bounds.centerX(), bounds.centerY(), radius, paint);
+ }
+ }
+
+ public void animateToPressed() {
+ scheduleSelf(opener, SystemClock.uptimeMillis() + 100);
+ mRunning = true;
+ }
+
+ public void animateToNormal() {
+ mOpen = false;
+ mRunning = false;
+ unscheduleSelf(opener);
+ invalidateSelf();
+ }
+
+ private Runnable opener = new Runnable() {
+ @Override
+ public void run() {
+ mOpen = true;
+ invalidateSelf();
+ mRunning = false;
+ }
+ };
+
+ @Override
+ public void start() {
+ //NOOP
+ }
+
+ @Override
+ public void stop() {
+ animateToNormal();
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/TrackOvalDrawable.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/TrackOvalDrawable.java
new file mode 100644
index 0000000..056973f
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/TrackOvalDrawable.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.support.annotation.NonNull;
+
+/**
+ * Simple {@link org.adw.library.widgets.discreteseekbar.internal.drawable.StateDrawable} implementation
+ * to draw circles/ovals
+ *
+ * @hide
+ */
+public class TrackOvalDrawable extends StateDrawable {
+ private RectF mRectF = new RectF();
+
+ public TrackOvalDrawable(@NonNull ColorStateList tintStateList) {
+ super(tintStateList);
+ }
+
+ @Override
+ void doDraw(Canvas canvas, Paint paint) {
+ mRectF.set(getBounds());
+ canvas.drawOval(mRectF, paint);
+ }
+
+}
diff --git a/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/TrackRectDrawable.java b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/TrackRectDrawable.java
new file mode 100644
index 0000000..cac2ae2
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/java/org/adw/library/widgets/discreteseekbar/internal/drawable/TrackRectDrawable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
+ *
+ * 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 org.adw.library.widgets.discreteseekbar.internal.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.support.annotation.NonNull;
+
+/**
+ * Simple {@link org.adw.library.widgets.discreteseekbar.internal.drawable.StateDrawable} implementation
+ * to draw rectangles
+ *
+ * @hide
+ */
+public class TrackRectDrawable extends StateDrawable {
+ public TrackRectDrawable(@NonNull ColorStateList tintStateList) {
+ super(tintStateList);
+ }
+
+ @Override
+ void doDraw(Canvas canvas, Paint paint) {
+ canvas.drawRect(getBounds(), paint);
+ }
+
+}
diff --git a/discrete-seek-bar-demo/library/src/main/res/color/dsb_progress_color_list.xml b/discrete-seek-bar-demo/library/src/main/res/color/dsb_progress_color_list.xml
new file mode 100644
index 0000000..bb5d5ac
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/res/color/dsb_progress_color_list.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/library/src/main/res/color/dsb_ripple_color_list.xml b/discrete-seek-bar-demo/library/src/main/res/color/dsb_ripple_color_list.xml
new file mode 100644
index 0000000..eb0c175
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/res/color/dsb_ripple_color_list.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/library/src/main/res/color/dsb_track_color_list.xml b/discrete-seek-bar-demo/library/src/main/res/color/dsb_track_color_list.xml
new file mode 100644
index 0000000..4774f8b
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/res/color/dsb_track_color_list.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/library/src/main/res/values/attrs.xml b/discrete-seek-bar-demo/library/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..a306362
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/res/values/attrs.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/library/src/main/res/values/styles.xml b/discrete-seek-bar-demo/library/src/main/res/values/styles.xml
new file mode 100644
index 0000000..44009ec
--- /dev/null
+++ b/discrete-seek-bar-demo/library/src/main/res/values/styles.xml
@@ -0,0 +1,39 @@
+
+
+
+ #ff009688
+ #ff939393
+ #66939393
+ #77939393
+ #99999999
+
+
+
+
+
+
diff --git a/discrete-seek-bar-demo/sample/.gitignore b/discrete-seek-bar-demo/sample/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/discrete-seek-bar-demo/sample/build.gradle b/discrete-seek-bar-demo/sample/build.gradle
new file mode 100644
index 0000000..891d979
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.1"
+
+ defaultConfig {
+ applicationId "org.adw.samples.discreteseekbar"
+ minSdkVersion 4
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+
+}
+
+dependencies {
+ compile project (':library')
+ compile 'com.jakewharton:butterknife:6.0.0'
+}
diff --git a/discrete-seek-bar-demo/sample/src/main/AndroidManifest.xml b/discrete-seek-bar-demo/sample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f6283f6
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/discrete-seek-bar-demo/sample/src/main/java/org/adw/samples/discreteseekbar/MainActivity.java b/discrete-seek-bar-demo/sample/src/main/java/org/adw/samples/discreteseekbar/MainActivity.java
new file mode 100644
index 0000000..1e4dc61
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/java/org/adw/samples/discreteseekbar/MainActivity.java
@@ -0,0 +1,217 @@
+package org.adw.samples.discreteseekbar;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Toast;
+
+import org.adw.library.widgets.discreteseekbar.DiscreteSeekBar;
+
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+
+public class MainActivity extends Activity implements View.OnClickListener{
+ private final static int TRACK = 0;
+ private final static int PROGRESS = 1;
+ private final static int FLOATER_START = 2;
+ private final static int FLOATER_END = 3;
+ @InjectView(R.id.sample_seekbar)
+ DiscreteSeekBar sbTarget;
+ @InjectView(R.id.rg_position)
+ RadioGroup rgPosition;//modify the color of different parts
+ @InjectView(R.id.rbt_track)
+ RadioButton rbtTrack;
+ @InjectView(R.id.rbt_progress_bar)
+ RadioButton rbtProgress;
+ @InjectView(R.id.rbt_floater_end)
+ RadioButton rbtFloaterEnd;
+ @InjectView(R.id.rbt_floater_start)
+ RadioButton rbtFloaterStart;
+ @InjectView(R.id.red_discrete)
+ DiscreteSeekBar sbRed;
+ @InjectView(R.id.green_discrete)
+ DiscreteSeekBar sbGreen;
+ @InjectView(R.id.blue_discrete)
+ DiscreteSeekBar sbBlue;
+ @InjectView(R.id.btn_set_color)
+ Button btnSetColor;
+ @InjectView(R.id.min_discrete)
+ DiscreteSeekBar sbMin;
+ @InjectView(R.id.max_discrete)
+ DiscreteSeekBar sbMax;
+ @InjectView(R.id.btn_set_value)
+ Button btnSetValue;
+ @InjectView(R.id.edt_regex)
+ EditText edtRegex;
+ @InjectView(R.id.btn_set_regex)
+ Button btnSetRegex;
+
+ int colorPos;
+ int iFloaterStartColor = -1;
+ int iFloaterEndColor = -1;
+ int iColor = 0xffffffff;
+
+ Context ctx;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ ButterKnife.inject(this);
+
+ ctx = this;
+
+ rgPosition.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ if(checkedId == rbtTrack.getId()){
+ colorPos = TRACK;
+ }else if(checkedId == rbtProgress.getId()){
+ colorPos = PROGRESS;
+ }else if(checkedId == rbtFloaterStart.getId()){
+ colorPos = FLOATER_START;
+ }else if(checkedId == rbtFloaterEnd.getId()){
+ colorPos = FLOATER_END;
+ }
+ }
+ });
+ rbtProgress.setChecked(true);
+
+ sbRed.setOnProgressChangeListener(colorListener);
+ sbGreen.setOnProgressChangeListener(colorListener);
+ sbBlue.setOnProgressChangeListener(colorListener);
+
+ btnSetColor.setOnClickListener(this);
+ btnSetRegex.setOnClickListener(this);
+ btnSetValue.setOnClickListener(this);
+
+
+ /**
+ * 初始化
+ */
+ sbTarget.setThumbColor(0xffFF8877, 0xff00ff00);
+ sbTarget.setScrubberColor(0xffff0000);
+ sbTarget.setNumericTransformer(new DiscreteSeekBar.NumericTransformer() {
+ @Override
+ public int transform(int value) {
+ return value * 100;
+ }
+ });
+ }
+
+ /**
+ * Called when a view has been clicked.
+ *
+ * @param v The view that was clicked.
+ */
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.btn_set_color:
+ switch (colorPos){
+ case TRACK:
+ /**
+ * 只能在xml中设置
+ */
+ Toast.makeText(ctx, "no API for track color", Toast.LENGTH_SHORT).show();
+ break;
+ case PROGRESS:
+ sbTarget.setScrubberColor(iColor);
+ break;
+ case FLOATER_START:
+ iFloaterStartColor = iColor;
+ if(iFloaterEndColor == -1) {
+ //这个接口有耦合,不爽
+ sbTarget.setThumbColor(iColor, iColor);
+ }else{
+ sbTarget.setThumbColor(iColor, iFloaterEndColor);
+ }
+ break;
+ case FLOATER_END:
+ iFloaterEndColor = iColor;
+ if(iFloaterStartColor == -1) {
+ sbTarget.setThumbColor(iColor, iColor);
+ }else{
+ sbTarget.setThumbColor(iFloaterStartColor, iColor);
+ }
+ break;
+ }
+ break;
+ case R.id.btn_set_value:
+ /**
+ * sbMin 会看到数字显示不全的BUG,因为0>-1000,但是floater的宽度是由Max设置的,所以....
+ */
+ sbTarget.setMin(sbMin.getProgress());
+ sbTarget.setMax(sbMax.getProgress());
+ break;
+ case R.id.btn_set_regex:
+ /**
+ * 如果想显示字符需要实现另外两个方法
+ */
+ sbTarget.setNumericTransformer(new DiscreteSeekBar.NumericTransformer() {
+ @Override
+ public int transform(int value) {
+ try {
+ float multiple = Float.parseFloat(edtRegex.getText().toString());
+ return (int)(value * multiple);
+ }catch (Exception e) {
+ return value;
+ }
+ }
+
+ /**
+ * Return the desired value to be shown to the user.
+ * This value will be displayed 'as is' without further formatting.
+ *
+ * @param value The value to be transformed
+ * @return A formatted string
+ */
+ @Override
+ public String transformToString(int value) {
+ return super.transformToString(value);
+ }
+
+ /**
+ * Used to indicate which transform will be used. If this method returns true,
+ * {@link #transformToString(int)} will be used, otherwise {@link #transform(int)}
+ * will be used
+ */
+ @Override
+ public boolean useStringTransform() {
+ return super.useStringTransform();
+ }
+ });
+ }
+ }
+
+ DiscreteSeekBar.OnProgressChangeListener colorListener = new DiscreteSeekBar.OnProgressChangeListener() {
+ @Override
+ public void onProgressChanged(DiscreteSeekBar seekBar, int value, boolean fromUser) {
+ if(seekBar.getId() == sbRed.getId()){
+ iColor = 0xff000000 + (value<<16) + (sbGreen.getProgress()<<8) + (sbBlue.getProgress());
+ }else if(seekBar.getId() == sbGreen.getId()){
+ iColor = 0xff000000 + (sbRed.getProgress()<<16) + (value<<8) + (sbBlue.getProgress());
+ }else{
+ iColor = 0xff000000 + (sbRed.getProgress()<<16) + (sbGreen.getProgress()<<8) + (value);
+ }
+
+ btnSetColor.setTextColor(iColor);
+ }
+
+ @Override
+ public void onStartTrackingTouch(DiscreteSeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(DiscreteSeekBar seekBar) {
+
+ }
+ };
+}
diff --git a/discrete-seek-bar-demo/sample/src/main/res/color/floater_blue.xml b/discrete-seek-bar-demo/sample/src/main/res/color/floater_blue.xml
new file mode 100644
index 0000000..4837aa8
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/color/floater_blue.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/sample/src/main/res/color/floater_green.xml b/discrete-seek-bar-demo/sample/src/main/res/color/floater_green.xml
new file mode 100644
index 0000000..1ea4057
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/color/floater_green.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/sample/src/main/res/color/floater_red.xml b/discrete-seek-bar-demo/sample/src/main/res/color/floater_red.xml
new file mode 100644
index 0000000..d7461ad
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/color/floater_red.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/sample/src/main/res/color/my_floater_color.xml b/discrete-seek-bar-demo/sample/src/main/res/color/my_floater_color.xml
new file mode 100644
index 0000000..fc0932b
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/color/my_floater_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/sample/src/main/res/color/my_progress_color.xml b/discrete-seek-bar-demo/sample/src/main/res/color/my_progress_color.xml
new file mode 100644
index 0000000..e635a83
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/color/my_progress_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/sample/src/main/res/color/progress_blue.xml b/discrete-seek-bar-demo/sample/src/main/res/color/progress_blue.xml
new file mode 100644
index 0000000..967ae1f
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/color/progress_blue.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/sample/src/main/res/color/progress_green.xml b/discrete-seek-bar-demo/sample/src/main/res/color/progress_green.xml
new file mode 100644
index 0000000..0305419
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/color/progress_green.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/sample/src/main/res/color/progress_red.xml b/discrete-seek-bar-demo/sample/src/main/res/color/progress_red.xml
new file mode 100644
index 0000000..54103fe
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/color/progress_red.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/sample/src/main/res/drawable-hdpi/ic_launcher.png b/discrete-seek-bar-demo/sample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/discrete-seek-bar-demo/sample/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/discrete-seek-bar-demo/sample/src/main/res/drawable-mdpi/ic_launcher.png b/discrete-seek-bar-demo/sample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
Binary files /dev/null and b/discrete-seek-bar-demo/sample/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/discrete-seek-bar-demo/sample/src/main/res/drawable-xhdpi/ic_launcher.png b/discrete-seek-bar-demo/sample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
Binary files /dev/null and b/discrete-seek-bar-demo/sample/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/discrete-seek-bar-demo/sample/src/main/res/drawable-xxhdpi/ic_launcher.png b/discrete-seek-bar-demo/sample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
Binary files /dev/null and b/discrete-seek-bar-demo/sample/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/discrete-seek-bar-demo/sample/src/main/res/layout/activity_main.xml b/discrete-seek-bar-demo/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..87898d8
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/discrete-seek-bar-demo/sample/src/main/res/values-v11/styles.xml b/discrete-seek-bar-demo/sample/src/main/res/values-v11/styles.xml
new file mode 100644
index 0000000..65a325d
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/values-v11/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/discrete-seek-bar-demo/sample/src/main/res/values-v21/styles.xml b/discrete-seek-bar-demo/sample/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..dba3c41
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/values-v21/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/discrete-seek-bar-demo/sample/src/main/res/values-w820dp/dimens.xml b/discrete-seek-bar-demo/sample/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/discrete-seek-bar-demo/sample/src/main/res/values/colors.xml b/discrete-seek-bar-demo/sample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..b1bf260
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #FFFF4422
+ #FF44bb44
+ #FF4466dd
+
+
\ No newline at end of file
diff --git a/discrete-seek-bar-demo/sample/src/main/res/values/dimens.xml b/discrete-seek-bar-demo/sample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/discrete-seek-bar-demo/sample/src/main/res/values/strings.xml b/discrete-seek-bar-demo/sample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..5cb16db
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/values/strings.xml
@@ -0,0 +1,21 @@
+
+
+
+ DiscreteSeekBar Sample
+ Customize Discrete Seek Bar
+ progress color
+ floater color
+ track color
+ Color Setting
+ floater color pressed
+ floater color animate
+ R:
+ G:
+ B:
+ SET COLOR
+ Value Setting
+ Min Value
+ Max Value
+ SET VALUE
+ Numeric Transformer
+
diff --git a/discrete-seek-bar-demo/sample/src/main/res/values/styles.xml b/discrete-seek-bar-demo/sample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..1b72f89
--- /dev/null
+++ b/discrete-seek-bar-demo/sample/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/discrete-seek-bar-demo/settings.gradle b/discrete-seek-bar-demo/settings.gradle
new file mode 100644
index 0000000..77c36d0
--- /dev/null
+++ b/discrete-seek-bar-demo/settings.gradle
@@ -0,0 +1 @@
+include ':library', ':sample'
diff --git a/dynamic-load-apk-demo/.gitignore b/dynamic-load-apk-demo/.gitignore
new file mode 100644
index 0000000..cbd2f9c
--- /dev/null
+++ b/dynamic-load-apk-demo/.gitignore
@@ -0,0 +1,7 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+*.iml
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/apk/host.apk b/dynamic-load-apk-demo/apk/host.apk
new file mode 100644
index 0000000..33e0f3c
Binary files /dev/null and b/dynamic-load-apk-demo/apk/host.apk differ
diff --git a/dynamic-load-apk-demo/apk/plugina-mode1.apk b/dynamic-load-apk-demo/apk/plugina-mode1.apk
new file mode 100644
index 0000000..98be580
Binary files /dev/null and b/dynamic-load-apk-demo/apk/plugina-mode1.apk differ
diff --git a/dynamic-load-apk-demo/apk/pluginb-mode2.apk b/dynamic-load-apk-demo/apk/pluginb-mode2.apk
new file mode 100644
index 0000000..bd122a7
Binary files /dev/null and b/dynamic-load-apk-demo/apk/pluginb-mode2.apk differ
diff --git a/dynamic-load-apk-demo/apk/pluginc-mode3.apk b/dynamic-load-apk-demo/apk/pluginc-mode3.apk
new file mode 100644
index 0000000..d12b521
Binary files /dev/null and b/dynamic-load-apk-demo/apk/pluginc-mode3.apk differ
diff --git a/dynamic-load-apk-demo/app/.gitignore b/dynamic-load-apk-demo/app/.gitignore
new file mode 100644
index 0000000..d0b97c6
--- /dev/null
+++ b/dynamic-load-apk-demo/app/.gitignore
@@ -0,0 +1,2 @@
+/build
+*.iml
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/app/build.gradle b/dynamic-load-apk-demo/app/build.gradle
new file mode 100644
index 0000000..32391e3
--- /dev/null
+++ b/dynamic-load-apk-demo/app/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ applicationId "com.ffish.dlapkdemo"
+ minSdkVersion 8
+ targetSdkVersion 19
+ versionCode 1
+ versionName "1.0"
+ }
+
+}
+
+dependencies {
+ compile project(':lib')
+ compile project(':dlapkplugininterface')
+}
diff --git a/dynamic-load-apk-demo/app/proguard-rules.pro b/dynamic-load-apk-demo/app/proguard-rules.pro
new file mode 100644
index 0000000..22cef25
--- /dev/null
+++ b/dynamic-load-apk-demo/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:\AndroidSDK\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/dynamic-load-apk-demo/app/src/androidTest/java/com/ffish/dlapkdemo/ApplicationTest.java b/dynamic-load-apk-demo/app/src/androidTest/java/com/ffish/dlapkdemo/ApplicationTest.java
new file mode 100644
index 0000000..f016c16
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/androidTest/java/com/ffish/dlapkdemo/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.ffish.dlapkdemo;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/app/src/main/AndroidManifest.xml b/dynamic-load-apk-demo/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f3e7dab
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/app/src/main/java/com/ffish/dlapkdemo/HostClassForModel3.java b/dynamic-load-apk-demo/app/src/main/java/com/ffish/dlapkdemo/HostClassForModel3.java
new file mode 100644
index 0000000..198f1b0
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/main/java/com/ffish/dlapkdemo/HostClassForModel3.java
@@ -0,0 +1,13 @@
+package com.ffish.dlapkdemo;
+
+import android.content.Context;
+import android.widget.Toast;
+
+/**
+ * Created by FFish on 2015/2/14.
+ */
+public class HostClassForModel3 {
+ public static void methodForModel3(Context context){
+ Toast.makeText(context, "模式3耦合度过高,请尽量不要用该模式开发...", Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/dynamic-load-apk-demo/app/src/main/java/com/ffish/dlapkdemo/MainActivity.java b/dynamic-load-apk-demo/app/src/main/java/com/ffish/dlapkdemo/MainActivity.java
new file mode 100644
index 0000000..f38eb05
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/main/java/com/ffish/dlapkdemo/MainActivity.java
@@ -0,0 +1,134 @@
+package com.ffish.dlapkdemo;
+
+import android.app.Activity;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.ffish.dlapkplugininterface.HostInterface;
+import com.ffish.dlapkplugininterface.InterfaceManager;
+import com.ryg.dynamicload.internal.DLIntent;
+import com.ryg.dynamicload.internal.DLPluginManager;
+import com.ryg.utils.DLUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+
+
+public class MainActivity extends Activity implements AdapterView.OnItemClickListener, HostInterface{
+
+ private ArrayList mApkItems = new ArrayList(5);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ initData();
+
+ GridView gridView = (GridView)findViewById(R.id.gridView);
+ if (mApkItems.size() == 0){
+ gridView.setVisibility(View.GONE);
+ TextView textView = (TextView)findViewById(R.id.noApkTip);
+ textView.setText("SDCard的/RootApkPath路径下无Apk文件");
+ textView.setVisibility(View.VISIBLE);
+ } else {
+ gridView.setAdapter(new ApkItemAdapter());
+ gridView.setOnItemClickListener(this);
+ }
+
+ // 向插件提供宿主的服务
+ InterfaceManager.getInstance().setHostInterface(this);
+ }
+
+ private void initData(){
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
+ DLUtils.showDialog(this, "提示", "sd卡未挂载");
+ return;
+ }
+
+ String rootApkPath = Environment.getExternalStorageDirectory() + "/RootApkPath";
+ File file = new File(rootApkPath);
+ if (!file.exists() || !file.isDirectory()){
+ boolean success = file.mkdir();
+ if (!success){
+ DLUtils.showDialog(this, "提示", "apk根目录创建失败");
+ return;
+ }
+ }
+ File[] apks = file.listFiles();
+ for (File apk:apks){
+ ApkItem item = new ApkItem();
+ PackageInfo packageInfo = DLUtils.getPackageInfo(this, apk.getAbsolutePath());
+ if (packageInfo.activities != null && packageInfo.activities.length > 0){
+ item.launchActivityName = packageInfo.activities[0].name;
+ }
+ item.packageInfo = packageInfo;
+ item.apkPath = apk.getAbsolutePath();
+ mApkItems.add(item);
+ DLPluginManager.getInstance(this).loadApk(item.apkPath);
+ }
+
+ }
+
+ private class ApkItem{
+ public String apkPath;
+ public PackageInfo packageInfo;
+ public String launchActivityName;
+ }
+
+ private class ApkItemAdapter extends BaseAdapter{
+ @Override
+ public int getCount() {
+ return mApkItems.size();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null){
+ ImageView imageView = new ImageView(parent.getContext());
+ imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+ TextView textView = new TextView(parent.getContext());
+
+ convertView = textView;
+ }
+
+ ApkItem item = (ApkItem)getItem(position);
+// ((ImageView)convertView).setImageDrawable(DLUtils.getAppIcon(parent.getContext(), item.apkPath));
+ ((TextView)convertView).setCompoundDrawablesWithIntrinsicBounds(null, DLUtils.getAppIcon(parent.getContext(), item.apkPath), null, null);
+ PackageManager pm = getApplicationContext().getPackageManager();
+ ((TextView) convertView).setText(pm.getApplicationLabel(item.packageInfo.applicationInfo));
+ return convertView;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mApkItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ ApkItem item = mApkItems.get(position);
+ DLPluginManager.getInstance(this).startPluginActivity(this, new DLIntent(item.packageInfo.packageName, item.launchActivityName));
+ }
+
+ @Override
+ public void methodInHost() {
+ Toast.makeText(this, "这里是宿主程序,您的访问请求已收到,正在处理...", Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/dynamic-load-apk-demo/app/src/main/res/layout/activity_main.xml b/dynamic-load-apk-demo/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..56958d7
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/app/src/main/res/layout/main_grid_item.xml b/dynamic-load-apk-demo/app/src/main/res/layout/main_grid_item.xml
new file mode 100644
index 0000000..c0eb3ab
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/main/res/layout/main_grid_item.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/dynamic-load-apk-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/dynamic-load-apk-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/dynamic-load-apk-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/dynamic-load-apk-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/dynamic-load-apk-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/dynamic-load-apk-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dynamic-load-apk-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/dynamic-load-apk-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/app/src/main/res/values-w820dp/dimens.xml b/dynamic-load-apk-demo/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/dynamic-load-apk-demo/app/src/main/res/values/dimens.xml b/dynamic-load-apk-demo/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/dynamic-load-apk-demo/app/src/main/res/values/strings.xml b/dynamic-load-apk-demo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8903b36
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+ DLApkDemo
+
+ Hello world!
+ Settings
+
diff --git a/dynamic-load-apk-demo/app/src/main/res/values/styles.xml b/dynamic-load-apk-demo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..e84bc5e
--- /dev/null
+++ b/dynamic-load-apk-demo/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/build.gradle b/dynamic-load-apk-demo/build.gradle
new file mode 100644
index 0000000..741f70e
--- /dev/null
+++ b/dynamic-load-apk-demo/build.gradle
@@ -0,0 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.0.1'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/dynamic-load-apk-demo/dlapkplugina/.gitignore b/dynamic-load-apk-demo/dlapkplugina/.gitignore
new file mode 100644
index 0000000..d0b97c6
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/.gitignore
@@ -0,0 +1,2 @@
+/build
+*.iml
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/dlapkplugina/build.gradle b/dynamic-load-apk-demo/dlapkplugina/build.gradle
new file mode 100644
index 0000000..94d0330
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ applicationId "com.ffish.dlapkplugina"
+ minSdkVersion 8
+ targetSdkVersion 19
+ versionCode 1
+ versionName "1.0"
+ }
+
+}
+
+dependencies {
+ provided fileTree(dir: 'dl-lib', include: ['*.jar'])
+}
diff --git a/dynamic-load-apk-demo/dlapkplugina/dl-lib/dl-lib.jar b/dynamic-load-apk-demo/dlapkplugina/dl-lib/dl-lib.jar
new file mode 100644
index 0000000..53c3b86
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkplugina/dl-lib/dl-lib.jar differ
diff --git a/dynamic-load-apk-demo/dlapkplugina/proguard-rules.pro b/dynamic-load-apk-demo/dlapkplugina/proguard-rules.pro
new file mode 100644
index 0000000..8e88735
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:/AndroidSDK/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/androidTest/java/com/ffish/dlapkplugina/ApplicationTest.java b/dynamic-load-apk-demo/dlapkplugina/src/androidTest/java/com/ffish/dlapkplugina/ApplicationTest.java
new file mode 100644
index 0000000..a14cb68
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/src/androidTest/java/com/ffish/dlapkplugina/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.ffish.dlapkplugina;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/AndroidManifest.xml b/dynamic-load-apk-demo/dlapkplugina/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..462b31f
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/java/com/ffish/dlapkplugina/MainActivity.java b/dynamic-load-apk-demo/dlapkplugina/src/main/java/com/ffish/dlapkplugina/MainActivity.java
new file mode 100644
index 0000000..9757afa
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/src/main/java/com/ffish/dlapkplugina/MainActivity.java
@@ -0,0 +1,16 @@
+package com.ffish.dlapkplugina;
+
+import android.os.Bundle;
+
+import com.ryg.dynamicload.DLBasePluginActivity;
+
+
+public class MainActivity extends DLBasePluginActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ }
+
+}
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/res/layout/activity_main.xml b/dynamic-load-apk-demo/dlapkplugina/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..07d0fc8
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/src/main/res/layout/activity_main.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-hdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-mdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-xhdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkplugina/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/res/values-w820dp/dimens.xml b/dynamic-load-apk-demo/dlapkplugina/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/res/values/dimens.xml b/dynamic-load-apk-demo/dlapkplugina/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/res/values/strings.xml b/dynamic-load-apk-demo/dlapkplugina/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8e2b328
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+ DLApkPluginA
+
+ Hello world!
+ Settings
+ 模式1:宿主和插件之间不通信
+
diff --git a/dynamic-load-apk-demo/dlapkplugina/src/main/res/values/styles.xml b/dynamic-load-apk-demo/dlapkplugina/src/main/res/values/styles.xml
new file mode 100644
index 0000000..e84bc5e
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugina/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkpluginb/.gitignore b/dynamic-load-apk-demo/dlapkpluginb/.gitignore
new file mode 100644
index 0000000..d0b97c6
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/.gitignore
@@ -0,0 +1,2 @@
+/build
+*.iml
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/dlapkpluginb/build.gradle b/dynamic-load-apk-demo/dlapkpluginb/build.gradle
new file mode 100644
index 0000000..6148ac3
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ applicationId "com.ffish.dlapkpluginb"
+ minSdkVersion 8
+ targetSdkVersion 19
+ versionCode 1
+ versionName "1.0"
+ }
+
+}
+
+repositories {
+ flatDir {
+ dirs 'dl-lib'
+ }
+}
+
+dependencies {
+ provided fileTree(dir: 'dl-lib', include: ['*.jar'])
+ provided(name: 'dlapkplugininterface-debug', ext: 'aar')
+// provided project(':dlapkplugininterface')
+}
diff --git a/dynamic-load-apk-demo/dlapkpluginb/dl-lib/dl-lib.jar b/dynamic-load-apk-demo/dlapkpluginb/dl-lib/dl-lib.jar
new file mode 100644
index 0000000..53c3b86
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginb/dl-lib/dl-lib.jar differ
diff --git a/dynamic-load-apk-demo/dlapkpluginb/dl-lib/dlapkplugininterface-debug.aar b/dynamic-load-apk-demo/dlapkpluginb/dl-lib/dlapkplugininterface-debug.aar
new file mode 100644
index 0000000..4da5567
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginb/dl-lib/dlapkplugininterface-debug.aar differ
diff --git a/dynamic-load-apk-demo/dlapkpluginb/proguard-rules.pro b/dynamic-load-apk-demo/dlapkpluginb/proguard-rules.pro
new file mode 100644
index 0000000..8e88735
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:/AndroidSDK/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/androidTest/java/com/ffish/dlapkpluginb/ApplicationTest.java b/dynamic-load-apk-demo/dlapkpluginb/src/androidTest/java/com/ffish/dlapkpluginb/ApplicationTest.java
new file mode 100644
index 0000000..3e1be6b
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/src/androidTest/java/com/ffish/dlapkpluginb/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.ffish.dlapkpluginb;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/AndroidManifest.xml b/dynamic-load-apk-demo/dlapkpluginb/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..204bb69
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/java/com/ffish/dlapkpluginb/MainActivity.java b/dynamic-load-apk-demo/dlapkpluginb/src/main/java/com/ffish/dlapkpluginb/MainActivity.java
new file mode 100644
index 0000000..ec22cfe
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/src/main/java/com/ffish/dlapkpluginb/MainActivity.java
@@ -0,0 +1,28 @@
+package com.ffish.dlapkpluginb;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.ffish.dlapkplugininterface.InterfaceManager;
+import com.ryg.dynamicload.DLBasePluginActivity;
+
+public class MainActivity extends DLBasePluginActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ that.setContentView(R.layout.activity_main);
+
+ Button button = (Button)findViewById(R.id.button);
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ InterfaceManager.getInstance().getHostInterface().methodInHost();
+ }
+ });
+ }
+
+}
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/res/layout/activity_main.xml b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..44e90e6
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/layout/activity_main.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-hdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-mdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-xhdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values-w820dp/dimens.xml b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values/dimens.xml b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values/strings.xml b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d3cd853
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+ DLApkPluginB
+
+ Hello world!
+ Settings
+ 模式2:插件可以通过接口访问宿主的方法
+ 点击向宿主发送方法调用请求
+
diff --git a/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values/styles.xml b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values/styles.xml
new file mode 100644
index 0000000..e84bc5e
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginb/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkpluginc/.gitignore b/dynamic-load-apk-demo/dlapkpluginc/.gitignore
new file mode 100644
index 0000000..d0b97c6
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/.gitignore
@@ -0,0 +1,2 @@
+/build
+*.iml
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/dlapkpluginc/build.gradle b/dynamic-load-apk-demo/dlapkpluginc/build.gradle
new file mode 100644
index 0000000..dbf201a
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ applicationId "com.ffish.dlapkpluginc"
+ minSdkVersion 8
+ targetSdkVersion 19
+ versionCode 1
+ versionName "1.0"
+ }
+
+}
+
+repositories {
+ flatDir {
+ dirs 'dl-lib'
+ }
+}
+
+dependencies {
+ provided fileTree(dir: 'dl-lib', include: ['*.jar'])
+ provided(name: 'app-debug', ext: 'aar')
+}
diff --git a/dynamic-load-apk-demo/dlapkpluginc/dl-lib/app-debug.aar b/dynamic-load-apk-demo/dlapkpluginc/dl-lib/app-debug.aar
new file mode 100644
index 0000000..0482186
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginc/dl-lib/app-debug.aar differ
diff --git a/dynamic-load-apk-demo/dlapkpluginc/dl-lib/dl-lib.jar b/dynamic-load-apk-demo/dlapkpluginc/dl-lib/dl-lib.jar
new file mode 100644
index 0000000..53c3b86
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginc/dl-lib/dl-lib.jar differ
diff --git a/dynamic-load-apk-demo/dlapkpluginc/proguard-rules.pro b/dynamic-load-apk-demo/dlapkpluginc/proguard-rules.pro
new file mode 100644
index 0000000..8e88735
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:/AndroidSDK/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/androidTest/java/com/ffish/dlapkpluginc/ApplicationTest.java b/dynamic-load-apk-demo/dlapkpluginc/src/androidTest/java/com/ffish/dlapkpluginc/ApplicationTest.java
new file mode 100644
index 0000000..d0b1eb9
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/src/androidTest/java/com/ffish/dlapkpluginc/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.ffish.dlapkpluginc;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/AndroidManifest.xml b/dynamic-load-apk-demo/dlapkpluginc/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7f4ce09
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/java/com/ffish/dlapkpluginc/MainActivity.java b/dynamic-load-apk-demo/dlapkpluginc/src/main/java/com/ffish/dlapkpluginc/MainActivity.java
new file mode 100644
index 0000000..07de0c5
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/src/main/java/com/ffish/dlapkpluginc/MainActivity.java
@@ -0,0 +1,27 @@
+package com.ffish.dlapkpluginc;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import com.ffish.dlapkdemo.HostClassForModel3;
+import com.ryg.dynamicload.DLBasePluginActivity;
+
+
+public class MainActivity extends DLBasePluginActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ Button button = (Button)findViewById(R.id.button);
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ HostClassForModel3.methodForModel3(that);
+ }
+ });
+ }
+
+}
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/res/layout/activity_main.xml b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..08d68ae
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/layout/activity_main.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-hdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-mdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-xhdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values-w820dp/dimens.xml b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values/dimens.xml b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values/strings.xml b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0164597
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+ dlapkpluginc
+
+ Hello world!
+ Settings
+ 模式3:插件依赖宿主
+ 点击向宿主发送方法调用请求
+
diff --git a/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values/styles.xml b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values/styles.xml
new file mode 100644
index 0000000..e84bc5e
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkpluginc/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkplugininterface/.gitignore b/dynamic-load-apk-demo/dlapkplugininterface/.gitignore
new file mode 100644
index 0000000..82261cd
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugininterface/.gitignore
@@ -0,0 +1,2 @@
+/build
+.iml
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/dlapkplugininterface/build.gradle b/dynamic-load-apk-demo/dlapkplugininterface/build.gradle
new file mode 100644
index 0000000..73281ee
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugininterface/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ minSdkVersion 8
+ targetSdkVersion 19
+ versionCode 1
+ versionName "1.0"
+ }
+}
diff --git a/dynamic-load-apk-demo/dlapkplugininterface/dlapkplugininterface.iml b/dynamic-load-apk-demo/dlapkplugininterface/dlapkplugininterface.iml
new file mode 100644
index 0000000..2ba09ad
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugininterface/dlapkplugininterface.iml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkplugininterface/proguard-rules.pro b/dynamic-load-apk-demo/dlapkplugininterface/proguard-rules.pro
new file mode 100644
index 0000000..8e88735
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugininterface/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:/AndroidSDK/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/dynamic-load-apk-demo/dlapkplugininterface/src/androidTest/java/com/ffish/dlapkplugininterface/ApplicationTest.java b/dynamic-load-apk-demo/dlapkplugininterface/src/androidTest/java/com/ffish/dlapkplugininterface/ApplicationTest.java
new file mode 100644
index 0000000..59994c7
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugininterface/src/androidTest/java/com/ffish/dlapkplugininterface/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.ffish.dlapkplugininterface;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/dlapkplugininterface/src/main/AndroidManifest.xml b/dynamic-load-apk-demo/dlapkplugininterface/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0161781
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugininterface/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/dlapkplugininterface/src/main/java/com/ffish/dlapkplugininterface/HostInterface.java b/dynamic-load-apk-demo/dlapkplugininterface/src/main/java/com/ffish/dlapkplugininterface/HostInterface.java
new file mode 100644
index 0000000..c8ae66d
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugininterface/src/main/java/com/ffish/dlapkplugininterface/HostInterface.java
@@ -0,0 +1,8 @@
+package com.ffish.dlapkplugininterface;
+
+/**
+ * Created by FFish on 2015/2/12.
+ */
+public interface HostInterface {
+ void methodInHost();
+}
diff --git a/dynamic-load-apk-demo/dlapkplugininterface/src/main/java/com/ffish/dlapkplugininterface/InterfaceManager.java b/dynamic-load-apk-demo/dlapkplugininterface/src/main/java/com/ffish/dlapkplugininterface/InterfaceManager.java
new file mode 100644
index 0000000..621bbcf
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugininterface/src/main/java/com/ffish/dlapkplugininterface/InterfaceManager.java
@@ -0,0 +1,26 @@
+package com.ffish.dlapkplugininterface;
+
+/**
+ * Created by FFish on 2015/2/12.
+ */
+public class InterfaceManager {
+ private static InterfaceManager sInstance;
+ private HostInterface mHostInterface;
+
+ private InterfaceManager(){}
+
+ public static InterfaceManager getInstance(){
+ if (sInstance == null){
+ sInstance = new InterfaceManager();
+ }
+ return sInstance;
+ }
+
+ public void setHostInterface(HostInterface hostInterface){
+ mHostInterface = hostInterface;
+ }
+
+ public HostInterface getHostInterface(){
+ return mHostInterface;
+ }
+}
diff --git a/dynamic-load-apk-demo/dlapkplugininterface/src/main/res/values/strings.xml b/dynamic-load-apk-demo/dlapkplugininterface/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a7373b5
--- /dev/null
+++ b/dynamic-load-apk-demo/dlapkplugininterface/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ DLApkPluginInterface
+
diff --git a/dynamic-load-apk-demo/gradle.properties b/dynamic-load-apk-demo/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/dynamic-load-apk-demo/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/gradle/wrapper/gradle-wrapper.jar b/dynamic-load-apk-demo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/dynamic-load-apk-demo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/dynamic-load-apk-demo/gradle/wrapper/gradle-wrapper.properties b/dynamic-load-apk-demo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/dynamic-load-apk-demo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/dynamic-load-apk-demo/gradlew b/dynamic-load-apk-demo/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/dynamic-load-apk-demo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/dynamic-load-apk-demo/gradlew.bat b/dynamic-load-apk-demo/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/dynamic-load-apk-demo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/dynamic-load-apk-demo/lib/.gitignore b/dynamic-load-apk-demo/lib/.gitignore
new file mode 100644
index 0000000..d0b97c6
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/.gitignore
@@ -0,0 +1,2 @@
+/build
+*.iml
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/lib/build.gradle b/dynamic-load-apk-demo/lib/build.gradle
new file mode 100644
index 0000000..f406481
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'com.android.library'
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:19.0.1'
+}
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "21.1.2"
+}
diff --git a/dynamic-load-apk-demo/lib/gradle/wrapper/gradle-wrapper.jar b/dynamic-load-apk-demo/lib/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/dynamic-load-apk-demo/lib/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/dynamic-load-apk-demo/lib/gradle/wrapper/gradle-wrapper.properties b/dynamic-load-apk-demo/lib/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1e61d1f
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
diff --git a/dynamic-load-apk-demo/lib/gradlew b/dynamic-load-apk-demo/lib/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/dynamic-load-apk-demo/lib/gradlew.bat b/dynamic-load-apk-demo/lib/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/listview-animations-demo/ListviewAnimationDemo/ic_launcher-web.png b/dynamic-load-apk-demo/lib/ic_launcher-web.png
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/ic_launcher-web.png
rename to dynamic-load-apk-demo/lib/ic_launcher-web.png
diff --git a/dynamic-load-apk-demo/lib/lib.iml b/dynamic-load-apk-demo/lib/lib.iml
new file mode 100644
index 0000000..de62635
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/lib.iml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/listview-animations-demo/ListviewAnimationLib/project.properties b/dynamic-load-apk-demo/lib/project.properties
similarity index 88%
rename from listview-animations-demo/ListviewAnimationLib/project.properties
rename to dynamic-load-apk-demo/lib/project.properties
index 6148244..91d2b02 100644
--- a/listview-animations-demo/ListviewAnimationLib/project.properties
+++ b/dynamic-load-apk-demo/lib/project.properties
@@ -13,4 +13,3 @@
# Project target.
target=android-19
android.library=true
-android.library.reference.1=../../../workspace/android-support-v7-appcompat
diff --git a/dynamic-load-apk-demo/lib/src/main/AndroidManifest.xml b/dynamic-load-apk-demo/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e6b83e7
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLBasePluginActivity.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLBasePluginActivity.java
new file mode 100644
index 0000000..8487de8
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLBasePluginActivity.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2014 singwhatiwanna(任玉刚)
+ *
+ * collaborator:田啸,宋思宇
+ *
+ * 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 com.ryg.dynamicload;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.ryg.dynamicload.internal.DLIntent;
+import com.ryg.dynamicload.internal.DLPluginManager;
+import com.ryg.dynamicload.internal.DLPluginPackage;
+import com.ryg.utils.DLConstants;
+
+/**
+ * note: can use that like this.
+ * @see {@link DLBasePluginActivity.that}
+ * @author renyugang
+ */
+public class DLBasePluginActivity extends Activity implements DLPlugin {
+
+ private static final String TAG = "DLBasePluginActivity";
+
+ /**
+ * 代理activity,可以当作Context来使用,会根据需要来决定是否指向this
+ */
+ protected Activity mProxyActivity;
+
+ /**
+ * 等同于mProxyActivity,可以当作Context来使用,会根据需要来决定是否指向this
+ * 可以当作this来使用
+ */
+ protected Activity that;
+ protected DLPluginManager mPluginManager;
+ protected DLPluginPackage mPluginPackage;
+
+ protected int mFrom = DLConstants.FROM_INTERNAL;
+
+ @Override
+ public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
+ Log.d(TAG, "attach: proxyActivity= " + proxyActivity);
+ mProxyActivity = (Activity)proxyActivity;
+ that = mProxyActivity;
+ mPluginPackage = pluginPackage;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mFrom = savedInstanceState.getInt(DLConstants.FROM, DLConstants.FROM_INTERNAL);
+ }
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onCreate(savedInstanceState);
+ mProxyActivity = this;
+ that = mProxyActivity;
+ }
+
+ mPluginManager = DLPluginManager.getInstance(that);
+ Log.d(TAG, "onCreate: from= " + (mFrom == DLConstants.FROM_INTERNAL ? "DLConstants.FROM_INTERNAL" : "FROM_EXTERNAL"));
+ }
+
+ @Override
+ public void setContentView(View view) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.setContentView(view);
+ } else {
+ mProxyActivity.setContentView(view);
+ }
+ }
+
+ @Override
+ public void setContentView(View view, LayoutParams params) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.setContentView(view, params);
+ } else {
+ mProxyActivity.setContentView(view, params);
+ }
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.setContentView(layoutResID);
+ } else {
+ mProxyActivity.setContentView(layoutResID);
+ }
+ }
+
+ @Override
+ public void addContentView(View view, LayoutParams params) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.addContentView(view, params);
+ } else {
+ mProxyActivity.addContentView(view, params);
+ }
+ }
+
+ @Override
+ public View findViewById(int id) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.findViewById(id);
+ } else {
+ return mProxyActivity.findViewById(id);
+ }
+ }
+
+ @Override
+ public Intent getIntent() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getIntent();
+ } else {
+ return mProxyActivity.getIntent();
+ }
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getClassLoader();
+ } else {
+ return mProxyActivity.getClassLoader();
+ }
+ }
+
+ @Override
+ public Resources getResources() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getResources();
+ } else {
+ return mProxyActivity.getResources();
+ }
+ }
+
+ @Override
+ public String getPackageName() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getPackageName();
+ } else {
+ return mPluginPackage.packageName;
+ }
+ }
+
+ @Override
+ public LayoutInflater getLayoutInflater() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getLayoutInflater();
+ } else {
+ return mProxyActivity.getLayoutInflater();
+ }
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getMenuInflater();
+ } else {
+ return mProxyActivity.getMenuInflater();
+ }
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getSharedPreferences(name, mode);
+ } else {
+ return mProxyActivity.getSharedPreferences(name, mode);
+ }
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getApplicationContext();
+ } else {
+ return mProxyActivity.getApplicationContext();
+ }
+ }
+
+ @Override
+ public WindowManager getWindowManager() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getWindowManager();
+ } else {
+ return mProxyActivity.getWindowManager();
+ }
+ }
+
+ @Override
+ public Window getWindow() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getWindow();
+ } else {
+ return mProxyActivity.getWindow();
+ }
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getSystemService(name);
+ } else {
+ return mProxyActivity.getSystemService(name);
+ }
+ }
+
+ @Override
+ public void finish() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.finish();
+ } else {
+ mProxyActivity.finish();
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onStart();
+ }
+ }
+
+ @Override
+ public void onRestart() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onRestart();
+ }
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onRestoreInstanceState(savedInstanceState);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onSaveInstanceState(outState);
+ }
+ }
+
+ public void onNewIntent(Intent intent) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onNewIntent(intent);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onResume();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onPause();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onStop();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onDestroy();
+ }
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.onTouchEvent(event);
+ }
+ return false;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.onKeyUp(keyCode, event);
+ }
+ return false;
+ }
+
+ public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onWindowAttributesChanged(params);
+ }
+ }
+
+ public void onWindowFocusChanged(boolean hasFocus) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onWindowFocusChanged(hasFocus);
+ }
+ }
+
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.onCreateOptionsMenu(menu);
+ }
+ return true;
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return onOptionsItemSelected(item);
+ }
+ return false;
+ }
+
+ /**
+ * @param dlIntent
+ * @return may be {@link #START_RESULT_SUCCESS}, {@link #START_RESULT_NO_PKG},
+ * {@link #START_RESULT_NO_CLASS}, {@link #START_RESULT_TYPE_ERROR}
+ */
+ public int startPluginActivity(DLIntent dlIntent) {
+ return startPluginActivityForResult(dlIntent, -1);
+ }
+
+ /**
+ * @param dlIntent
+ * @return may be {@link #START_RESULT_SUCCESS}, {@link #START_RESULT_NO_PKG},
+ * {@link #START_RESULT_NO_CLASS}, {@link #START_RESULT_TYPE_ERROR}
+ */
+ public int startPluginActivityForResult(DLIntent dlIntent, int requestCode) {
+ if (mFrom == DLConstants.FROM_EXTERNAL) {
+ if (dlIntent.getPluginPackage() == null) {
+ dlIntent.setPluginPackage(mPluginPackage.packageName);
+ }
+ }
+ return mPluginManager.startPluginActivityForResult(that, dlIntent, requestCode);
+ }
+
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLBasePluginFragmentActivity.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLBasePluginFragmentActivity.java
new file mode 100644
index 0000000..67c5d9c
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLBasePluginFragmentActivity.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2014 singwhatiwanna(任玉刚)
+ *
+ * collaborator:田啸,宋思宇
+ *
+ * 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 com.ryg.dynamicload;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.LoaderManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.ryg.dynamicload.internal.DLIntent;
+import com.ryg.dynamicload.internal.DLPluginManager;
+import com.ryg.dynamicload.internal.DLPluginPackage;
+import com.ryg.utils.DLConstants;
+
+public class DLBasePluginFragmentActivity extends FragmentActivity implements DLPlugin {
+
+ private static final String TAG = "DLBasePluginFragmentActivity";
+
+ /**
+ * 代理FragmentActivity,可以当作Context来使用,会根据需要来决定是否指向this
+ */
+ protected FragmentActivity mProxyActivity;
+
+ /**
+ * 等同于mProxyActivity,可以当作Context来使用,会根据需要来决定是否指向this
+ * 可以当作this来使用
+ */
+ protected FragmentActivity that;
+ protected int mFrom = DLConstants.FROM_INTERNAL;
+ protected DLPluginManager mPluginManager;
+ protected DLPluginPackage mPluginPackage;
+
+ @Override
+ public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
+ Log.d(TAG, "attach: proxyActivity= " + proxyActivity);
+ mProxyActivity = (FragmentActivity) proxyActivity;
+ that = mProxyActivity;
+ mPluginPackage = pluginPackage;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mFrom = savedInstanceState.getInt(DLConstants.FROM, DLConstants.FROM_INTERNAL);
+ }
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onCreate(savedInstanceState);
+ mProxyActivity = this;
+ that = mProxyActivity;
+ }
+
+ mPluginManager = DLPluginManager.getInstance(that);
+ Log.d(TAG, "onCreate: from= "
+ + (mFrom == DLConstants.FROM_INTERNAL ? "DLConstants.FROM_INTERNAL" : "FROM_EXTERNAL"));
+ }
+
+ @Override
+ public void setContentView(View view) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.setContentView(view);
+ } else {
+ mProxyActivity.setContentView(view);
+ }
+ }
+
+ @Override
+ public void setContentView(View view, LayoutParams params) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.setContentView(view, params);
+ } else {
+ mProxyActivity.setContentView(view, params);
+ }
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.setContentView(layoutResID);
+ } else {
+ mProxyActivity.setContentView(layoutResID);
+ }
+ }
+
+ @Override
+ public void addContentView(View view, LayoutParams params) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.addContentView(view, params);
+ } else {
+ mProxyActivity.addContentView(view, params);
+ }
+ }
+
+ @Override
+ public View findViewById(int id) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.findViewById(id);
+ } else {
+ return mProxyActivity.findViewById(id);
+ }
+ }
+
+ @Override
+ public Intent getIntent() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getIntent();
+ } else {
+ return mProxyActivity.getIntent();
+ }
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getClassLoader();
+ } else {
+ return mProxyActivity.getClassLoader();
+ }
+ }
+
+ @Override
+ public Resources getResources() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getResources();
+ } else {
+ return mProxyActivity.getResources();
+ }
+ }
+
+ @Override
+ public String getPackageName() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getPackageName();
+ } else {
+ return mPluginPackage.packageName;
+ }
+ }
+
+ @Override
+ public LayoutInflater getLayoutInflater() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getLayoutInflater();
+ } else {
+ return mProxyActivity.getLayoutInflater();
+ }
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getMenuInflater();
+ } else {
+ return mProxyActivity.getMenuInflater();
+ }
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getSharedPreferences(name, mode);
+ } else {
+ return mProxyActivity.getSharedPreferences(name, mode);
+ }
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getApplicationContext();
+ } else {
+ return mProxyActivity.getApplicationContext();
+ }
+ }
+
+ @Override
+ public WindowManager getWindowManager() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getWindowManager();
+ } else {
+ return mProxyActivity.getWindowManager();
+ }
+ }
+
+ @Override
+ public Window getWindow() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getWindow();
+ } else {
+ return mProxyActivity.getWindow();
+ }
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getSystemService(name);
+ } else {
+ return mProxyActivity.getSystemService(name);
+ }
+ }
+
+ @Override
+ public void finish() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.finish();
+ } else {
+ mProxyActivity.finish();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public void onStart() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onStart();
+ }
+ }
+
+ @Override
+ public void onRestart() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onRestart();
+ }
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onRestoreInstanceState(savedInstanceState);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onSaveInstanceState(outState);
+ }
+ }
+
+ public void onNewIntent(Intent intent) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onNewIntent(intent);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onResume();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onPause();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onStop();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onDestroy();
+ }
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.onTouchEvent(event);
+ }
+ return false;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.onKeyUp(keyCode, event);
+ }
+ return false;
+ }
+
+ public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onWindowAttributesChanged(params);
+ }
+ }
+
+ public void onWindowFocusChanged(boolean hasFocus) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ super.onWindowFocusChanged(hasFocus);
+ }
+ }
+
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.onCreateOptionsMenu(menu);
+ }
+ return true;
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return onOptionsItemSelected(item);
+ }
+ return false;
+ }
+
+ /**
+ * @param dlIntent
+ * @return may be {@link #START_RESULT_SUCCESS}, {@link #START_RESULT_NO_PKG},
+ * {@link #START_RESULT_NO_CLASS}, {@link #START_RESULT_TYPE_ERROR}
+ */
+ public int startPluginActivity(DLIntent dlIntent) {
+ return startPluginActivityForResult(dlIntent, -1);
+ }
+
+ /**
+ * @param dlIntent
+ * @return may be {@link #START_RESULT_SUCCESS}, {@link #START_RESULT_NO_PKG},
+ * {@link #START_RESULT_NO_CLASS}, {@link #START_RESULT_TYPE_ERROR}
+ */
+ public int startPluginActivityForResult(DLIntent dlIntent, int requestCode) {
+ if (mFrom == DLConstants.FROM_EXTERNAL) {
+ if (dlIntent.getPluginPackage() == null) {
+ dlIntent.setPluginPackage(mPluginPackage.packageName);
+ }
+ }
+ return mPluginManager.startPluginActivityForResult(that, dlIntent, requestCode);
+ }
+
+ // ------------------------------------------------------------------------
+ // methods override from FragmentActivity
+ // ------------------------------------------------------------------------
+
+ @Override
+ public FragmentManager getSupportFragmentManager() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getSupportFragmentManager();
+ }
+ return mProxyActivity.getSupportFragmentManager();
+ }
+
+ @Override
+ public LoaderManager getSupportLoaderManager() {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ return super.getSupportLoaderManager();
+ }
+ return mProxyActivity.getSupportLoaderManager();
+ }
+
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLPlugin.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLPlugin.java
new file mode 100644
index 0000000..fd00997
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLPlugin.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 singwhatiwanna(任玉刚)
+ *
+ * collaborator:田啸,宋思宇
+ *
+ * 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 com.ryg.dynamicload;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.WindowManager.LayoutParams;
+
+import com.ryg.dynamicload.internal.DLPluginPackage;
+
+public interface DLPlugin {
+
+ public void onCreate(Bundle savedInstanceState);
+ public void onStart();
+ public void onRestart();
+ public void onActivityResult(int requestCode, int resultCode, Intent data);
+ public void onResume();
+ public void onPause();
+ public void onStop();
+ public void onDestroy();
+ public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);
+ public void onSaveInstanceState(Bundle outState);
+ public void onNewIntent(Intent intent);
+ public void onRestoreInstanceState(Bundle savedInstanceState);
+ public boolean onTouchEvent(MotionEvent event);
+ public boolean onKeyUp(int keyCode, KeyEvent event);
+ public void onWindowAttributesChanged(LayoutParams params);
+ public void onWindowFocusChanged(boolean hasFocus);
+ public void onBackPressed();
+ public boolean onCreateOptionsMenu(Menu menu);
+ public boolean onOptionsItemSelected(MenuItem item);
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLProxyActivity.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLProxyActivity.java
new file mode 100644
index 0000000..e688113
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLProxyActivity.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2014 singwhatiwanna(任玉刚)
+ *
+ * collaborator:田啸,宋思宇
+ *
+ * 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 com.ryg.dynamicload;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.WindowManager.LayoutParams;
+
+import com.ryg.dynamicload.internal.DLPluginManager;
+import com.ryg.dynamicload.internal.DLProxyImpl;
+import com.ryg.dynamicload.internal.DLProxyImpl.DLProxy;
+
+public class DLProxyActivity extends Activity implements DLProxy {
+
+ protected DLPlugin mRemoteActivity;
+ private DLProxyImpl impl = new DLProxyImpl(this);
+ private DLPluginManager mPluginManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ impl.onCreate(getIntent());
+ }
+
+ @Override
+ public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
+ mRemoteActivity = remoteActivity;
+ mPluginManager = pluginManager;
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ return impl.getAssets() == null ? super.getAssets() : impl.getAssets();
+ }
+
+ @Override
+ public Resources getResources() {
+ return impl.getResources() == null ? super.getResources() : impl.getResources();
+ }
+
+ @Override
+ public Theme getTheme() {
+ return impl.getTheme() == null ? super.getTheme() : impl.getTheme();
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return impl.getClassLoader();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mRemoteActivity.onActivityResult(requestCode, resultCode, data);
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ protected void onStart() {
+ mRemoteActivity.onStart();
+ super.onStart();
+ }
+
+ @Override
+ protected void onRestart() {
+ mRemoteActivity.onRestart();
+ super.onRestart();
+ }
+
+ @Override
+ protected void onResume() {
+ mRemoteActivity.onResume();
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ mRemoteActivity.onPause();
+ super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ mRemoteActivity.onStop();
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mRemoteActivity.onDestroy();
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ mRemoteActivity.onSaveInstanceState(outState);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ mRemoteActivity.onRestoreInstanceState(savedInstanceState);
+ super.onRestoreInstanceState(savedInstanceState);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ mRemoteActivity.onNewIntent(intent);
+ super.onNewIntent(intent);
+ }
+
+ @Override
+ public void onBackPressed() {
+ mRemoteActivity.onBackPressed();
+ super.onBackPressed();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+ return mRemoteActivity.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ super.onKeyUp(keyCode, event);
+ return mRemoteActivity.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public void onWindowAttributesChanged(LayoutParams params) {
+ mRemoteActivity.onWindowAttributesChanged(params);
+ super.onWindowAttributesChanged(params);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ mRemoteActivity.onWindowFocusChanged(hasFocus);
+ super.onWindowFocusChanged(hasFocus);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ mRemoteActivity.onCreateOptionsMenu(menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ mRemoteActivity.onOptionsItemSelected(item);
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLProxyFragmentActivity.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLProxyFragmentActivity.java
new file mode 100644
index 0000000..63ba19f
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/DLProxyFragmentActivity.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2014 singwhatiwanna(任玉刚)
+ *
+ * collaborator:田啸,宋思宇
+ *
+ * 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 com.ryg.dynamicload;
+
+import android.content.Intent;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.WindowManager.LayoutParams;
+
+import com.ryg.dynamicload.internal.DLPluginManager;
+import com.ryg.dynamicload.internal.DLProxyImpl;
+import com.ryg.dynamicload.internal.DLProxyImpl.DLProxy;
+
+public class DLProxyFragmentActivity extends FragmentActivity implements DLProxy {
+
+ protected DLPlugin mRemoteActivity;
+ private DLProxyImpl impl = new DLProxyImpl(this);
+ private DLPluginManager mPluginManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ impl.onCreate(getIntent());
+ }
+
+ @Override
+ public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
+ mRemoteActivity = remoteActivity;
+ mPluginManager = pluginManager;
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ return impl.getAssets() == null ? super.getAssets() : impl.getAssets();
+ }
+
+ @Override
+ public Resources getResources() {
+ return impl.getResources() == null ? super.getResources() : impl.getResources();
+ }
+
+ @Override
+ public Theme getTheme() {
+ return impl.getTheme() == null ? super.getTheme() : impl.getTheme();
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return impl.getClassLoader();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mRemoteActivity.onActivityResult(requestCode, resultCode, data);
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ protected void onStart() {
+ mRemoteActivity.onStart();
+ super.onStart();
+ }
+
+ @Override
+ protected void onRestart() {
+ mRemoteActivity.onRestart();
+ super.onRestart();
+ }
+
+ @Override
+ protected void onResume() {
+ mRemoteActivity.onResume();
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ mRemoteActivity.onPause();
+ super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ mRemoteActivity.onStop();
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mRemoteActivity.onDestroy();
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ mRemoteActivity.onSaveInstanceState(outState);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ mRemoteActivity.onRestoreInstanceState(savedInstanceState);
+ super.onRestoreInstanceState(savedInstanceState);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ mRemoteActivity.onNewIntent(intent);
+ super.onNewIntent(intent);
+ }
+
+ @Override
+ public void onBackPressed() {
+ mRemoteActivity.onBackPressed();
+ super.onBackPressed();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+ return mRemoteActivity.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ super.onKeyUp(keyCode, event);
+ return mRemoteActivity.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public void onWindowAttributesChanged(LayoutParams params) {
+ mRemoteActivity.onWindowAttributesChanged(params);
+ super.onWindowAttributesChanged(params);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ mRemoteActivity.onWindowFocusChanged(hasFocus);
+ super.onWindowFocusChanged(hasFocus);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ mRemoteActivity.onCreateOptionsMenu(menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ mRemoteActivity.onOptionsItemSelected(item);
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLIntent.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLIntent.java
new file mode 100644
index 0000000..0e058c9
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLIntent.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 singwhatiwanna(任玉刚)
+ *
+ * collaborator:田啸,宋思宇
+ *
+ * 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 com.ryg.dynamicload.internal;
+
+import android.content.Intent;
+
+public class DLIntent extends Intent {
+
+ private String mPluginPackage;
+ private String mPluginClass;
+
+ public DLIntent() {
+ super();
+ }
+
+ public DLIntent(String pluginPackage) {
+ super();
+ this.mPluginPackage = pluginPackage;
+ }
+
+ public DLIntent(String pluginPackage, String pluginClass) {
+ super();
+ this.mPluginPackage = pluginPackage;
+ this.mPluginClass = pluginClass;
+ }
+
+ public DLIntent(String pluginPackage, Class> clazz) {
+ super();
+ this.mPluginPackage = pluginPackage;
+ this.mPluginClass = clazz.getName();
+ }
+
+ public String getPluginPackage() {
+ return mPluginPackage;
+ }
+
+ public void setPluginPackage(String pluginPackage) {
+ this.mPluginPackage = pluginPackage;
+ }
+
+ public String getPluginClass() {
+ return mPluginClass;
+ }
+
+ public void setPluginClass(String pluginClass) {
+ this.mPluginClass = pluginClass;
+ }
+
+ public void setPluginClass(Class> clazz) {
+ this.mPluginClass = clazz.getName();
+ }
+
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLPluginManager.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLPluginManager.java
new file mode 100644
index 0000000..fba529a
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLPluginManager.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2014 singwhatiwanna(任玉刚)
+ *
+ * collaborator:田啸,宋思宇
+ *
+ * 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 com.ryg.dynamicload.internal;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.Log;
+
+import com.ryg.dynamicload.DLBasePluginActivity;
+import com.ryg.dynamicload.DLBasePluginFragmentActivity;
+import com.ryg.dynamicload.DLProxyActivity;
+import com.ryg.dynamicload.DLProxyFragmentActivity;
+import com.ryg.utils.DLConstants;
+
+import dalvik.system.DexClassLoader;
+
+public class DLPluginManager {
+
+ private static final String TAG = "DLPluginManager";
+
+ /**
+ * return value of {@link #startPluginActivity(Activity, DLIntent)} start success
+ */
+ public static final int START_RESULT_SUCCESS = 0;
+
+ /**
+ * return value of {@link #startPluginActivity(Activity, DLIntent)} package not found
+ */
+ public static final int START_RESULT_NO_PKG = 1;
+
+ /**
+ * return value of {@link #startPluginActivity(Activity, DLIntent)} class not found
+ */
+ public static final int START_RESULT_NO_CLASS = 2;
+
+ /**
+ * return value of {@link #startPluginActivity(Activity, DLIntent)} class type error
+ */
+ public static final int START_RESULT_TYPE_ERROR = 3;
+
+
+ private static DLPluginManager sInstance;
+ private Context mContext;
+ private final HashMap mPackagesHolder = new HashMap();
+
+ private int mFrom = DLConstants.FROM_INTERNAL;
+
+ private DLPluginManager(Context context) {
+ mContext = context.getApplicationContext();
+ }
+
+ public static DLPluginManager getInstance(Context context) {
+ if (sInstance == null) {
+ synchronized (DLPluginManager.class) {
+ if (sInstance == null) {
+ sInstance = new DLPluginManager(context);
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Load a apk. Before start a plugin Activity, we should do this first.
+ * NOTE : will only be called by host apk.
+ * @param dexPath
+ */
+ public DLPluginPackage loadApk(String dexPath) {
+ // when loadApk is called by host apk, we assume that plugin is invoked by host.
+ mFrom = DLConstants.FROM_EXTERNAL;
+
+ PackageInfo packageInfo = mContext.getPackageManager().
+ getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
+ if (packageInfo == null)
+ return null;
+
+ final String packageName = packageInfo.packageName;
+ DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
+ if (pluginPackage == null) {
+ DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
+ AssetManager assetManager = createAssetManager(dexPath);
+ Resources resources = createResources(assetManager);
+ pluginPackage = new DLPluginPackage(packageName, dexPath, dexClassLoader, assetManager,
+ resources, packageInfo);
+ mPackagesHolder.put(packageName, pluginPackage);
+ }
+ return pluginPackage;
+ }
+
+ private DexClassLoader createDexClassLoader(String dexPath) {
+ File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
+ final String dexOutputPath = dexOutputDir.getAbsolutePath();
+ DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, null, mContext.getClassLoader());
+ return loader;
+ }
+
+ private AssetManager createAssetManager(String dexPath) {
+ try {
+ AssetManager assetManager = AssetManager.class.newInstance();
+ Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
+ addAssetPath.invoke(assetManager, dexPath);
+ return assetManager;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+ public DLPluginPackage getPackage(String packageName) {
+ return mPackagesHolder.get(packageName);
+ }
+
+ private Resources createResources(AssetManager assetManager) {
+ Resources superRes = mContext.getResources();
+ Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(),
+ superRes.getConfiguration());
+ return resources;
+ }
+
+ /**
+ * {@link #startPluginActivityForResult(Activity, DLIntent, int)}
+ */
+ public int startPluginActivity(Context context, DLIntent dlIntent) {
+ return startPluginActivityForResult(context, dlIntent, -1);
+ }
+
+ /**
+ * @param context
+ * @param dlIntent
+ * @param requestCode
+ * @return One of below: {@link #START_RESULT_SUCCESS} {@link #START_RESULT_NO_PKG}
+ * {@link #START_RESULT_NO_CLASS} {@link #START_RESULT_TYPE_ERROR}
+ */
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
+ if (mFrom == DLConstants.FROM_INTERNAL) {
+ dlIntent.setClassName(context, dlIntent.getPluginClass());
+ performStartActivityForResult(context, dlIntent, requestCode);
+ return DLPluginManager.START_RESULT_SUCCESS;
+ }
+
+ String packageName = dlIntent.getPluginPackage();
+ if (packageName == null) {
+ throw new NullPointerException("disallow null packageName.");
+ }
+ DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
+ if (pluginPackage == null) {
+ return START_RESULT_NO_PKG;
+ }
+
+ DexClassLoader classLoader = pluginPackage.classLoader;
+ String className = dlIntent.getPluginClass();
+ className = (className == null ? pluginPackage.getDefaultActivity() : className);
+ if (className.startsWith(".")) {
+ className = packageName + className;
+ }
+ Class> clazz = null;
+ try {
+ clazz = classLoader.loadClass(className);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ return START_RESULT_NO_CLASS;
+ }
+
+ Class extends Activity> activityClass = null;
+ if (DLBasePluginActivity.class.isAssignableFrom(clazz)) {
+ activityClass = DLProxyActivity.class;
+ } else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) {
+ activityClass = DLProxyFragmentActivity.class;
+ } else {
+ return START_RESULT_TYPE_ERROR;
+ }
+
+ dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
+ dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
+ dlIntent.setClass(mContext, activityClass);
+ performStartActivityForResult(context, dlIntent, requestCode);
+ return START_RESULT_SUCCESS;
+ }
+
+ private void performStartActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
+ Log.d(TAG, "launch " + dlIntent.getPluginClass());
+ if (context instanceof Activity) {
+ ((Activity) context).startActivityForResult(dlIntent, requestCode);
+ } else {
+ context.startActivity(dlIntent);
+ }
+ }
+
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLPluginPackage.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLPluginPackage.java
new file mode 100644
index 0000000..f3407cb
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLPluginPackage.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 singwhatiwanna(任玉刚)
+ *
+ * collaborator:田啸,宋思宇
+ *
+ * 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 com.ryg.dynamicload.internal;
+
+import android.content.pm.PackageInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import dalvik.system.DexClassLoader;
+
+/**
+ * A plugin apk. Activities in a same apk share a same AssetManager, Resources and DexClassLoader.
+ *
+ * @author siyu.song
+ */
+public class DLPluginPackage {
+
+ public String packageName;
+ private String mDefaultActivity;
+ public String path;
+
+ public DexClassLoader classLoader;
+ public AssetManager assetManager;
+ public Resources resources;
+ public PackageInfo packageInfo;
+
+ public DLPluginPackage(String packageName, String path, DexClassLoader loader, AssetManager assetManager,
+ Resources resources, PackageInfo packageInfo) {
+ this.packageName = packageName;
+ this.path = path;
+ this.classLoader = loader;
+ this.assetManager = assetManager;
+ this.resources = resources;
+ this.packageInfo = packageInfo;
+ }
+
+ public String getDefaultActivity() {
+ if (packageInfo.activities != null && packageInfo.activities.length > 0) {
+ mDefaultActivity = packageInfo.activities[0].name;
+ }
+ return mDefaultActivity;
+ }
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLProxyImpl.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLProxyImpl.java
new file mode 100644
index 0000000..060f9e5
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/dynamicload/internal/DLProxyImpl.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2014 singwhatiwanna(任玉刚)
+ *
+ * collaborator:田啸,宋思宇
+ *
+ * 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 com.ryg.dynamicload.internal;
+
+import java.lang.reflect.Constructor;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.ryg.dynamicload.DLPlugin;
+import com.ryg.utils.DLConstants;
+
+public class DLProxyImpl {
+
+ private static final String TAG = "DLProxyImpl";
+
+ private String mClass;
+ private String mPackageName;
+
+ private DLPluginPackage mPluginPackage;
+ private DLPluginManager mPluginManager;
+
+ private AssetManager mAssetManager;
+ private Resources mResources;
+ private Theme mTheme;
+
+ private ActivityInfo mActivityInfo;
+ private Activity mActivity;
+ protected DLPlugin mRemoteActivity;
+
+ public DLProxyImpl(Activity activity) {
+ mActivity = activity;
+ }
+
+ private void initializeActivityInfo() {
+ PackageInfo packageInfo = mPluginPackage.packageInfo;
+ if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) {
+ if (mClass == null) {
+ mClass = packageInfo.activities[0].name;
+ }
+ for (ActivityInfo a : packageInfo.activities) {
+ if (a.name.equals(mClass)) {
+ mActivityInfo = a;
+ }
+ }
+ }
+ }
+
+ private void handleActivityInfo() {
+ Log.d(TAG, "handleActivityInfo, theme=" + mActivityInfo.theme);
+ if (mActivityInfo.theme > 0) {
+ mActivity.setTheme(mActivityInfo.theme);
+ }
+ Theme superTheme = mActivity.getTheme();
+ mTheme = mResources.newTheme();
+ mTheme.setTo(superTheme);
+
+ // TODO: handle mActivityInfo.launchMode here in the future.
+ }
+
+ public void onCreate(Intent intent) {
+ mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
+ mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);
+ Log.d(TAG, "mClass=" + mClass + " mPackageName=" + mPackageName);
+
+ mPluginManager = DLPluginManager.getInstance(mActivity);
+ mPluginPackage = mPluginManager.getPackage(mPackageName);
+ mAssetManager = mPluginPackage.assetManager;
+ mResources = mPluginPackage.resources;
+
+ initializeActivityInfo();
+ handleActivityInfo();
+ launchTargetActivity();
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ protected void launchTargetActivity() {
+ try {
+ Class> localClass = getClassLoader().loadClass(mClass);
+ Constructor> localConstructor = localClass.getConstructor(new Class[] {});
+ Object instance = localConstructor.newInstance(new Object[] {});
+ mRemoteActivity = (DLPlugin) instance;
+ ((DLProxy) mActivity).attach(mRemoteActivity, mPluginManager);
+ Log.d(TAG, "instance = " + instance);
+
+ mRemoteActivity.attach(mActivity, mPluginPackage);
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
+ mRemoteActivity.onCreate(bundle);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public ClassLoader getClassLoader() {
+ return mPluginPackage.classLoader;
+ }
+
+ public AssetManager getAssets() {
+ return mAssetManager;
+ }
+
+ public Resources getResources() {
+ return mResources;
+ }
+
+ public Theme getTheme() {
+ return mTheme;
+ }
+
+ public DLPlugin getRemoteActivity() {
+ return mRemoteActivity;
+ }
+
+ public interface DLProxy {
+ public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager);
+ }
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/utils/DLConstants.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/utils/DLConstants.java
new file mode 100644
index 0000000..eb37625
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/utils/DLConstants.java
@@ -0,0 +1,23 @@
+package com.ryg.utils;
+
+public class DLConstants {
+ public static final String FROM = "extra.from";
+ public static final int FROM_INTERNAL = 0;
+ public static final int FROM_EXTERNAL = 1;
+
+ public static final String EXTRA_DEX_PATH = "extra.dex.path";
+ public static final String EXTRA_CLASS = "extra.class";
+ public static final String EXTRA_PACKAGE = "extra.package";
+
+ public static final int ACTIVITY_TYPE_UNKNOWN = -1;
+ public static final int ACTIVITY_TYPE_NORMAL = 1;
+ public static final int ACTIVITY_TYPE_FRAGMENT = 2;
+ public static final int ACTIVITY_TYPE_ACTIONBAR = 3;
+
+ public static final String PROXY_ACTIVITY_VIEW_ACTION =
+ "com.ryg.dynamicload.proxy.activity.VIEW";
+ public static final String PROXY_FRAGMENT_ACTIVITY_VIEW_ACTION =
+ "com.ryg.dynamicload.proxy.fragmentactivity.VIEW";
+
+ public static final String BRAND_SAMSUNG = "samsung";
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/java/com/ryg/utils/DLUtils.java b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/utils/DLUtils.java
new file mode 100644
index 0000000..09b3c0f
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/java/com/ryg/utils/DLUtils.java
@@ -0,0 +1,143 @@
+package com.ryg.utils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Log;
+
+import com.ryg.dynamicload.DLBasePluginActivity;
+import com.ryg.dynamicload.DLBasePluginFragmentActivity;
+
+public class DLUtils {
+ private static final String TAG = "DLUtils";
+
+ public static PackageInfo getPackageInfo(Context context, String apkFilepath) {
+ PackageManager pm = context.getPackageManager();
+ PackageInfo pkgInfo = null;
+ try {
+ pkgInfo = pm.getPackageArchiveInfo(apkFilepath, PackageManager.GET_ACTIVITIES);
+ } catch (Exception e) {
+ // should be something wrong with parse
+ e.printStackTrace();
+ }
+
+ return pkgInfo;
+ }
+
+ public static Drawable getAppIcon(Context context, String apkFilepath) {
+ PackageManager pm = context.getPackageManager();
+ PackageInfo pkgInfo = getPackageInfo(context, apkFilepath);
+ if (pkgInfo == null) {
+ return null;
+ }
+
+ // Workaround for http://code.google.com/p/android/issues/detail?id=9151
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
+ if (Build.VERSION.SDK_INT >= 8) {
+ appInfo.sourceDir = apkFilepath;
+ appInfo.publicSourceDir = apkFilepath;
+ }
+
+ return pm.getApplicationIcon(appInfo);
+ }
+
+ public static CharSequence getAppLabel(Context context, String apkFilepath) {
+ PackageManager pm = context.getPackageManager();
+ PackageInfo pkgInfo = getPackageInfo(context, apkFilepath);
+ if (pkgInfo == null) {
+ return null;
+ }
+
+ // Workaround for http://code.google.com/p/android/issues/detail?id=9151
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
+ if (Build.VERSION.SDK_INT >= 8) {
+ appInfo.sourceDir = apkFilepath;
+ appInfo.publicSourceDir = apkFilepath;
+ }
+
+ return pm.getApplicationLabel(appInfo);
+ }
+
+ public static String getProxyViewAction(String className, ClassLoader classLoader) {
+ int activityType = getActivityType(className, classLoader);
+ return getProxyViewActionByActivityType(activityType);
+ }
+
+ public static String getProxyViewAction(Class> cls) {
+ int activityType = getActivityType(cls);
+ return getProxyViewActionByActivityType(activityType);
+ }
+
+ private static String getProxyViewActionByActivityType(int activityType) {
+ String proxyViewAction = null;
+
+ switch (activityType) {
+ case DLConstants.ACTIVITY_TYPE_NORMAL: {
+ proxyViewAction = DLConstants.PROXY_ACTIVITY_VIEW_ACTION;
+ break;
+ }
+ case DLConstants.ACTIVITY_TYPE_FRAGMENT: {
+ proxyViewAction = DLConstants.PROXY_FRAGMENT_ACTIVITY_VIEW_ACTION;
+ break;
+ }
+ case DLConstants.ACTIVITY_TYPE_ACTIONBAR:
+ case DLConstants.ACTIVITY_TYPE_UNKNOWN:
+ default:
+ break;
+ }
+
+ if (proxyViewAction == null) {
+ Log.e(TAG, "unsupported activityType:" + activityType);
+ }
+
+ return proxyViewAction;
+ }
+
+ private static int getActivityType(String className, ClassLoader classLoader) {
+ int activityType = DLConstants.ACTIVITY_TYPE_UNKNOWN;
+
+ try {
+ Class> cls = Class.forName(className, false, classLoader);
+ activityType = getActivityType(cls);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ return activityType;
+ }
+
+ private static int getActivityType(Class> cls) {
+ int activityType = DLConstants.ACTIVITY_TYPE_UNKNOWN;
+
+ try {
+ if (cls.asSubclass(DLBasePluginActivity.class) != null) {
+ activityType = DLConstants.ACTIVITY_TYPE_NORMAL;
+ return activityType;
+ }
+ } catch (ClassCastException e) {
+ // ignored
+ }
+
+ try {
+ if (cls.asSubclass(DLBasePluginFragmentActivity.class) != null) {
+ activityType = DLConstants.ACTIVITY_TYPE_FRAGMENT;
+ return activityType;
+ }
+ } catch (ClassCastException e) {
+ // ignored
+ }
+
+ //TODO: handle other activity types, ActionbarActivity,eg.
+ return activityType;
+ }
+
+ public static void showDialog(Activity activity, String title, String message) {
+ new AlertDialog.Builder(activity).setTitle(title).setMessage(message)
+ .setPositiveButton("确定", null).show();
+ }
+}
diff --git a/dynamic-load-apk-demo/lib/src/main/res/values/dimens.xml b/dynamic-load-apk-demo/lib/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..55c1e59
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/res/values/dimens.xml
@@ -0,0 +1,7 @@
+
+
+
+ 16dp
+ 16dp
+
+
diff --git a/dynamic-load-apk-demo/lib/src/main/res/values/styles.xml b/dynamic-load-apk-demo/lib/src/main/res/values/styles.xml
new file mode 100644
index 0000000..6ce89c7
--- /dev/null
+++ b/dynamic-load-apk-demo/lib/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/dynamic-load-apk-demo/settings.gradle b/dynamic-load-apk-demo/settings.gradle
new file mode 100644
index 0000000..b580cc4
--- /dev/null
+++ b/dynamic-load-apk-demo/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':dlapkplugina', ':dlapkplugininterface', ':dlapkpluginb', ':dlapkpluginc', ":lib"
diff --git a/fly-refresh-demo/.gitignore b/fly-refresh-demo/.gitignore
new file mode 100644
index 0000000..9c4de58
--- /dev/null
+++ b/fly-refresh-demo/.gitignore
@@ -0,0 +1,7 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/fly-refresh-demo/FlyRefresh.iml b/fly-refresh-demo/FlyRefresh.iml
new file mode 100644
index 0000000..b0f7578
--- /dev/null
+++ b/fly-refresh-demo/FlyRefresh.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fly-refresh-demo/LICENSE b/fly-refresh-demo/LICENSE
new file mode 100644
index 0000000..4fdb8c3
--- /dev/null
+++ b/fly-refresh-demo/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 race604
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/fly-refresh-demo/README.md b/fly-refresh-demo/README.md
new file mode 100644
index 0000000..17255a7
--- /dev/null
+++ b/fly-refresh-demo/README.md
@@ -0,0 +1,8 @@
+FlyRefresh Demo
+====================
+###1. Demo Download
+本地下载
+###2. Screenshot
+
+###3. Document
+[How to Use FlyRefresh](https://github.com/aosp-exchange-group/android-open-project-analysis/tree/master/view/list-view/fly-refresh)
diff --git a/fly-refresh-demo/apk/fly-refresh-demo.apk b/fly-refresh-demo/apk/fly-refresh-demo.apk
new file mode 100644
index 0000000..cd8d4f5
Binary files /dev/null and b/fly-refresh-demo/apk/fly-refresh-demo.apk differ
diff --git a/fly-refresh-demo/apk/fly-refresh-demo.gif b/fly-refresh-demo/apk/fly-refresh-demo.gif
new file mode 100644
index 0000000..3e6eafe
Binary files /dev/null and b/fly-refresh-demo/apk/fly-refresh-demo.gif differ
diff --git a/fly-refresh-demo/app/.gitignore b/fly-refresh-demo/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/fly-refresh-demo/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/fly-refresh-demo/app/app.iml b/fly-refresh-demo/app/app.iml
new file mode 100644
index 0000000..ad52e81
--- /dev/null
+++ b/fly-refresh-demo/app/app.iml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fly-refresh-demo/app/build.gradle b/fly-refresh-demo/app/build.gradle
new file mode 100644
index 0000000..184784e
--- /dev/null
+++ b/fly-refresh-demo/app/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "com.skyace.test.flyrefreshtest"
+ minSdkVersion 14
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:22.1.1'
+ compile 'com.race604.flyrefresh:library:1.0.1'
+ compile 'com.android.support:recyclerview-v7:+'
+}
diff --git a/fly-refresh-demo/app/proguard-rules.pro b/fly-refresh-demo/app/proguard-rules.pro
new file mode 100644
index 0000000..e60e6c2
--- /dev/null
+++ b/fly-refresh-demo/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in E:\Android Studio SDK/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/fly-refresh-demo/app/src/androidTest/java/com/skyace/test/flyrefreshtest/ApplicationTest.java b/fly-refresh-demo/app/src/androidTest/java/com/skyace/test/flyrefreshtest/ApplicationTest.java
new file mode 100644
index 0000000..3280d09
--- /dev/null
+++ b/fly-refresh-demo/app/src/androidTest/java/com/skyace/test/flyrefreshtest/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.skyace.test.flyrefreshtest;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/fly-refresh-demo/app/src/main/AndroidManifest.xml b/fly-refresh-demo/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..189ca4d
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fly-refresh-demo/app/src/main/java/com/skyace/test/flyrefreshtest/MainActivity.java b/fly-refresh-demo/app/src/main/java/com/skyace/test/flyrefreshtest/MainActivity.java
new file mode 100644
index 0000000..f481d39
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/java/com/skyace/test/flyrefreshtest/MainActivity.java
@@ -0,0 +1,80 @@
+package com.skyace.test.flyrefreshtest;
+
+import android.app.Activity;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+
+import com.race604.flyrefresh.FlyRefreshLayout;
+
+import java.util.ArrayList;
+
+public class MainActivity extends Activity {
+
+ private FlyRefreshLayout frl;
+ private RecyclerView rv;
+ private MyAdapter myAdapter;
+
+// private ArrayAdapter adapter;
+
+ private ArrayList mDataset;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ frl = (FlyRefreshLayout) findViewById(R.id.fly_layout);
+ rv = (RecyclerView) findViewById(R.id.list);
+// adapter = new ArrayAdapter();
+// adapter.add();
+// mDataset = new int[] { R.drawable.image, R.drawable.image, //
+// R.drawable.image, R.drawable.image };
+ mDataset = new ArrayList();
+ mDataset.add(R.drawable.image1);
+ mDataset.add(R.drawable.image2);
+ mDataset.add(R.drawable.image3);
+ mDataset.add(R.drawable.image4);
+
+
+ LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
+ linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
+ rv.setLayoutManager(linearLayoutManager);
+
+ myAdapter = new MyAdapter(mDataset);
+ rv.setAdapter(myAdapter);
+
+ frl.setOnPullRefreshListener(new FlyRefreshLayout.OnPullRefreshListener() {
+ @Override
+ public void onRefresh(FlyRefreshLayout flyRefreshLayout) {
+ new AsyncTask(){
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ mDataset.add(0,R.drawable.refresh_image);//һ¼
+ myAdapter.notifyDataSetChanged();
+ frl.onRefreshFinish();
+ }
+
+ }.execute();
+ }
+
+
+ @Override
+ public void onRefreshAnimationEnd(FlyRefreshLayout flyRefreshLayout) {
+ System.out.println("Refresh end");
+ }
+ });
+ }
+}
diff --git a/fly-refresh-demo/app/src/main/java/com/skyace/test/flyrefreshtest/MyAdapter.java b/fly-refresh-demo/app/src/main/java/com/skyace/test/flyrefreshtest/MyAdapter.java
new file mode 100644
index 0000000..cc0791a
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/java/com/skyace/test/flyrefreshtest/MyAdapter.java
@@ -0,0 +1,71 @@
+package com.skyace.test.flyrefreshtest;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+
+/**
+ * Created by Administrator on 2015/7/30 0030.
+ */
+public class MyAdapter extends RecyclerView.Adapter {
+
+ private ArrayList mDataset; // 洫
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+
+ ImageView mImageView;
+
+ // TODO Auto-generated method stub
+ public ViewHolder(View v) {
+ super(v);
+ }
+
+ }
+
+ public MyAdapter(ArrayList mDataset) {
+ this.mDataset = mDataset;
+ }
+
+ /**
+ * ȡܵĿ
+ */
+ @Override
+ public int getItemCount() {
+ // TODO Auto-generated method stub
+ return mDataset.size();
+ }
+
+ /**
+ * һĿ
+ */
+// public void addItem(){
+// mDataset.add(R.drawable.refresh_image);
+// this.notifyDataSetChanged();
+// }
+
+
+ /**
+ * ViewHolder
+ */
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ // TODO Auto-generated method stub
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycleview, parent, false);
+ ViewHolder holder = new ViewHolder(v);
+ holder.mImageView = (ImageView) v.findViewById(R.id.iv_image);
+ return holder;
+ }
+
+ /**
+ * ݰViewHolder
+ */
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ // TODO Auto-generated method stub
+ holder.mImageView.setImageResource(mDataset.get(position));
+ }
+}
\ No newline at end of file
diff --git a/fly-refresh-demo/app/src/main/res/drawable/image1.jpg b/fly-refresh-demo/app/src/main/res/drawable/image1.jpg
new file mode 100644
index 0000000..6ef0e17
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/drawable/image1.jpg differ
diff --git a/fly-refresh-demo/app/src/main/res/drawable/image2.jpg b/fly-refresh-demo/app/src/main/res/drawable/image2.jpg
new file mode 100644
index 0000000..b9ac6ac
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/drawable/image2.jpg differ
diff --git a/fly-refresh-demo/app/src/main/res/drawable/image3.jpg b/fly-refresh-demo/app/src/main/res/drawable/image3.jpg
new file mode 100644
index 0000000..f60758d
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/drawable/image3.jpg differ
diff --git a/fly-refresh-demo/app/src/main/res/drawable/image4.jpg b/fly-refresh-demo/app/src/main/res/drawable/image4.jpg
new file mode 100644
index 0000000..e629cc3
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/drawable/image4.jpg differ
diff --git a/fly-refresh-demo/app/src/main/res/drawable/image6.jpg b/fly-refresh-demo/app/src/main/res/drawable/image6.jpg
new file mode 100644
index 0000000..fd6e8e8
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/drawable/image6.jpg differ
diff --git a/fly-refresh-demo/app/src/main/res/drawable/refresh_image.jpg b/fly-refresh-demo/app/src/main/res/drawable/refresh_image.jpg
new file mode 100644
index 0000000..938a37d
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/drawable/refresh_image.jpg differ
diff --git a/fly-refresh-demo/app/src/main/res/layout/activity_main.xml b/fly-refresh-demo/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..0ff7540
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/fly-refresh-demo/app/src/main/res/layout/item_recycleview.xml b/fly-refresh-demo/app/src/main/res/layout/item_recycleview.xml
new file mode 100644
index 0000000..95e6ff5
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/res/layout/item_recycleview.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/fly-refresh-demo/app/src/main/res/layout/recycler.xml b/fly-refresh-demo/app/src/main/res/layout/recycler.xml
new file mode 100644
index 0000000..4cd449e
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/res/layout/recycler.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/fly-refresh-demo/app/src/main/res/menu/menu_main.xml b/fly-refresh-demo/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..b1cb908
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,6 @@
+
diff --git a/fly-refresh-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/fly-refresh-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/fly-refresh-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/fly-refresh-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/fly-refresh-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/fly-refresh-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/fly-refresh-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/fly-refresh-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/fly-refresh-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/fly-refresh-demo/app/src/main/res/values-w820dp/dimens.xml b/fly-refresh-demo/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/fly-refresh-demo/app/src/main/res/values/dimens.xml b/fly-refresh-demo/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/fly-refresh-demo/app/src/main/res/values/strings.xml b/fly-refresh-demo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..eae267b
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+ FlyRefreshTest
+
+ Hello world!
+ Settings
+
diff --git a/fly-refresh-demo/app/src/main/res/values/styles.xml b/fly-refresh-demo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..766ab99
--- /dev/null
+++ b/fly-refresh-demo/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/fly-refresh-demo/build.gradle b/fly-refresh-demo/build.gradle
new file mode 100644
index 0000000..573f768
--- /dev/null
+++ b/fly-refresh-demo/build.gradle
@@ -0,0 +1,20 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.2.3'
+
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/fly-refresh-demo/gradle.properties b/fly-refresh-demo/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/fly-refresh-demo/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/fly-refresh-demo/gradle/gradle/wrapper/gradle-wrapper.jar b/fly-refresh-demo/gradle/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/fly-refresh-demo/gradle/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/fly-refresh-demo/gradle/gradle/wrapper/gradle-wrapper.properties b/fly-refresh-demo/gradle/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/fly-refresh-demo/gradle/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/fly-refresh-demo/gradlew b/fly-refresh-demo/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/fly-refresh-demo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/fly-refresh-demo/gradlew.bat b/fly-refresh-demo/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/fly-refresh-demo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/fly-refresh-demo/settings.gradle b/fly-refresh-demo/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/fly-refresh-demo/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/fresco-demo/.gitignore b/fresco-demo/.gitignore
new file mode 100644
index 0000000..afbdab3
--- /dev/null
+++ b/fresco-demo/.gitignore
@@ -0,0 +1,6 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
diff --git a/fresco-demo/README.md b/fresco-demo/README.md
new file mode 100644
index 0000000..561fb8a
--- /dev/null
+++ b/fresco-demo/README.md
@@ -0,0 +1,7 @@
+Fresco Demo
+====================
+###1. Demo Download
+[本地下载](apk/fresco_demo.apk?raw=true "点击下载到本地")
+
+###2. Screenshot
+
\ No newline at end of file
diff --git a/fresco-demo/apk/fresco_demo.apk b/fresco-demo/apk/fresco_demo.apk
new file mode 100644
index 0000000..868cf32
Binary files /dev/null and b/fresco-demo/apk/fresco_demo.apk differ
diff --git a/fresco-demo/apk/fresco_demo.gif b/fresco-demo/apk/fresco_demo.gif
new file mode 100644
index 0000000..1f9fde9
Binary files /dev/null and b/fresco-demo/apk/fresco_demo.gif differ
diff --git a/fresco-demo/app/.gitignore b/fresco-demo/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/fresco-demo/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/fresco-demo/app/app.iml b/fresco-demo/app/app.iml
new file mode 100644
index 0000000..0ad5912
--- /dev/null
+++ b/fresco-demo/app/app.iml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fresco-demo/app/build.gradle b/fresco-demo/app/build.gradle
new file mode 100644
index 0000000..b96abe8
--- /dev/null
+++ b/fresco-demo/app/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ applicationId "com.yql.fresco"
+ minSdkVersion 9
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.facebook.fresco:fresco:0.1.0+'
+ compile 'com.facebook.fresco:imagepipeline-okhttp:0.1.0+'
+}
diff --git a/fresco-demo/app/proguard-rules.pro b/fresco-demo/app/proguard-rules.pro
new file mode 100644
index 0000000..8c93d1f
--- /dev/null
+++ b/fresco-demo/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/yangqili/android/develop/adt-bundle-linux-x86_64-20140702/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/fresco-demo/app/src/androidTest/java/com/yql/fresco/ApplicationTest.java b/fresco-demo/app/src/androidTest/java/com/yql/fresco/ApplicationTest.java
new file mode 100644
index 0000000..2e3c6c7
--- /dev/null
+++ b/fresco-demo/app/src/androidTest/java/com/yql/fresco/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.yql.fresco;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/fresco-demo/app/src/main/AndroidManifest.xml b/fresco-demo/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..51f1fc9
--- /dev/null
+++ b/fresco-demo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fresco-demo/app/src/main/java/com/yql/fresco/AdvanceActivity.java b/fresco-demo/app/src/main/java/com/yql/fresco/AdvanceActivity.java
new file mode 100644
index 0000000..dc958b7
--- /dev/null
+++ b/fresco-demo/app/src/main/java/com/yql/fresco/AdvanceActivity.java
@@ -0,0 +1,240 @@
+package com.yql.fresco;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.facebook.drawee.backends.pipeline.Fresco;
+import com.facebook.drawee.drawable.ScalingUtils;
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.interfaces.DraweeController;
+import com.facebook.drawee.view.SimpleDraweeView;
+import com.facebook.imagepipeline.core.ImagePipelineConfig;
+import com.facebook.imagepipeline.decoder.ProgressiveJpegConfig;
+import com.facebook.imagepipeline.image.ImmutableQualityInfo;
+import com.facebook.imagepipeline.image.QualityInfo;
+import com.facebook.imagepipeline.request.BaseRepeatedPostProcessor;
+import com.facebook.imagepipeline.request.ImageRequest;
+import com.facebook.imagepipeline.request.ImageRequestBuilder;
+
+
+public class AdvanceActivity extends Activity {
+ public static final String PROGRESSIVE_JPEG = "http://s.lowendshare.com/0/1357767852.513.bridge.jpg";
+ private SimpleDraweeView simpleDraweeView;
+ private GenericDraweeHierarchyBuilder builder;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ initFresco();
+ setContentView(R.layout.activity_advance);
+ initDraweeHierarchy();
+ loadProgressvieJpeg();
+ }
+
+ private void initFresco() {
+ //渐进式JPEG配置
+// ProgressiveJpegConfig progressiveJpegConfig = new SimpleProgressiveJpegConfig();
+ ProgressiveJpegConfig progressiveJpegConfig = new ProgressiveJpegConfig() {
+ @Override
+ public int getNextScanNumberToDecode(int i) {
+ return i + 2;
+ }
+
+ @Override
+ public QualityInfo getQualityInfo(int i) {
+ boolean isGoodEnough = (i >= 5);
+ return ImmutableQualityInfo.of(i, isGoodEnough, false);
+ }
+ };
+ /**
+ *这里可以使用第三方网络请求库
+ */
+// OkHttpClient okHttpClient = new OkHttpClient();
+// ImagePipelineConfig imagePipelineConfig = OkHttpImagePipelineConfigFactory.newBuilder(this, okHttpClient)
+ //下面这个是Android默认的网络请求
+ ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig.newBuilder(this)
+ .setProgressiveJpegConfig(progressiveJpegConfig)
+ .build();
+ Fresco.initialize(this, imagePipelineConfig);
+ }
+
+ private void initDraweeHierarchy() {
+ simpleDraweeView = (SimpleDraweeView) findViewById(R.id.draweeview);
+ simpleDraweeView.setAspectRatio(1f);
+ builder = new GenericDraweeHierarchyBuilder(getResources());
+ GenericDraweeHierarchy hierarchy = builder.setFadeDuration(100)
+ .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
+ .setPlaceholderImage(getResources().getDrawable(R.color.placeholder))
+ .setFailureImage(getResources().getDrawable(R.color.error))
+ .setRetryImage(getResources().getDrawable(R.color.retrying))
+ .build();
+ simpleDraweeView.setHierarchy(hierarchy);
+ }
+
+ /**
+ * 加载渐进式jpeg
+ */
+ private void loadProgressvieJpeg() {
+ Uri uri = Uri.parse(PROGRESSIVE_JPEG);
+ ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
+ //这里设置渐进式jpeg开关,记得在fresco初始化时设置progressiveJpegConfig
+ .setProgressiveRenderingEnabled(true)
+// .setResizeOptions(new ResizeOptions(width, height))在解码之前修改图片尺寸
+// .setAutoRotateEnabled(true)如果jpeg带有方向信息,则自动旋转图片
+ .setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
+ .build();
+ DraweeController controller = Fresco.newDraweeControllerBuilder()
+ .setImageRequest(imageRequest)
+ .setTapToRetryEnabled(true)
+ .setOldController(simpleDraweeView.getController())
+ .build();
+ simpleDraweeView.setController(controller);
+ }
+
+ /**
+ * 多图加载,先显示低清图,再显示高清图
+ */
+ private void loadLow2HighImages() {
+ Uri lowUri = Uri.parse("http://img4.imgtn.bdimg.com/it/u=2938912401,3712734065&fm=21&gp=0.jpg");
+ Uri highUri = Uri.parse("http://www.33lc.com/article/UploadPic/2012-10/201210611525117694.jpg");
+ DraweeController controller = Fresco.newDraweeControllerBuilder()
+ .setLowResImageRequest(ImageRequest.fromUri(lowUri))
+ .setImageRequest(ImageRequest.fromUri(highUri))
+ .setTapToRetryEnabled(true)
+ .setOldController(simpleDraweeView.getController())
+ .build();
+ simpleDraweeView.setController(controller);
+ }
+
+ /**
+ * 预加载缩略图,仅支持本地URI,并且是JPEG图片格式(要有EXIF缩略图)
+ */
+ private void loadThumbnailPreview() {
+ Uri uri = Uri.parse("res://" + getResources().getResourcePackageName(R.drawable.loal_exif) + "/" + R.drawable.loal_exif);
+ ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
+ .setLocalThumbnailPreviewsEnabled(true)
+ .build();
+ DraweeController controller = Fresco.newDraweeControllerBuilder()
+ .setImageRequest(imageRequest)
+ .setOldController(simpleDraweeView.getController())
+ .build();
+ simpleDraweeView.setController(controller);
+ }
+
+ /**
+ * 优先显示队列(对本地图片进行复用)
+ */
+ private void loadFirstAvailableImg() {
+ Uri uri0 = Uri.parse("res://" + getResources().getResourcePackageName(R.drawable.local_first_330_220) + "/" + R.drawable.local_first_330_220);
+ Uri uri1 = Uri.parse("http://img.wallpapersking.com/d7/2012-8/2012081210374.jpg");
+ ImageRequest imageRequest0 = ImageRequest.fromUri(uri0);
+ ImageRequest imageRequest1 = ImageRequest.fromUri(uri1);
+ ImageRequest[] imageRequests = {imageRequest0, imageRequest1};
+ DraweeController controller = Fresco.newDraweeControllerBuilder()
+ .setFirstAvailableImageRequests(imageRequests)
+ .setOldController(simpleDraweeView.getController())
+ .build();
+ simpleDraweeView.setController(controller);
+ }
+
+ /**
+ * 修改图片
+ */
+ private void loadModifyImg() {
+ Uri uri = Uri.parse("res://" + getResources().getResourcePackageName(R.raw.local_img) + "/" + R.raw.local_img);
+// Postprocessor postprocessor = new Postprocessor() {
+// @Override
+// public void process(Bitmap bitmap) {
+// for (int x = 0; x < bitmap.getWidth(); x++) {
+// for (int y = 0; y < bitmap.getHeight(); y+=50) {
+// bitmap.setPixel(x, y, Color.RED);
+// }
+// }
+// }
+//
+// @Override
+// public String getName() {
+// return "modifyImg";
+// }
+// };
+ MyPostProcessor postprocessor = new MyPostProcessor();
+ ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
+ .setPostprocessor(postprocessor)
+ .build();
+ DraweeController controller = Fresco.newDraweeControllerBuilder()
+ .setImageRequest(imageRequest)
+ .setOldController(simpleDraweeView.getController())
+ .build();
+ simpleDraweeView.setController(controller);
+
+ postprocessor.setColor(Color.YELLOW);
+// postprocessor.setColor(Color.RED);
+ }
+
+ /**
+ * 多处理器
+ */
+ public class MyPostProcessor extends BaseRepeatedPostProcessor {
+ int color = Color.TRANSPARENT;
+
+ public void setColor(int color) {
+ this.color = color;
+ update();//记得要update
+ }
+
+ @Override
+ public void process(Bitmap bitmap) {
+ for (int x = 0; x < bitmap.getWidth(); x++) {
+ for (int y = 0; y < bitmap.getHeight(); y += 50) {
+ bitmap.setPixel(x, y, color);
+ }
+ }
+ }
+
+ @Override
+ public String getName() {
+ return "MyPostProcessor";
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_advance, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_progressive_jpeg) {
+ loadProgressvieJpeg();
+ return true;
+ } else if (id == R.id.action_low_to_high) {
+ loadLow2HighImages();
+ return true;
+ } else if (id == R.id.action_thumb_preview) {
+ loadThumbnailPreview();
+ return true;
+ } else if (id == R.id.action_first_availabel) {
+ loadFirstAvailableImg();
+ return true;
+ } else if (id == R.id.action_modify_img) {
+ loadModifyImg();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/fresco-demo/app/src/main/java/com/yql/fresco/MainActivity.java b/fresco-demo/app/src/main/java/com/yql/fresco/MainActivity.java
new file mode 100644
index 0000000..68c7171
--- /dev/null
+++ b/fresco-demo/app/src/main/java/com/yql/fresco/MainActivity.java
@@ -0,0 +1,41 @@
+package com.yql.fresco;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+
+public class MainActivity extends Activity implements View.OnClickListener {
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ findViewById(R.id.btn_in_xml).setOnClickListener(this);
+ findViewById(R.id.btn_in_code).setOnClickListener(this);
+ findViewById(R.id.btn_advance).setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.btn_in_xml:
+ startSimpleItent(SimpleActivity.TYPE_XML);
+ break;
+ case R.id.btn_in_code:
+ startSimpleItent(SimpleActivity.TYPE_CODE);
+ break;
+ case R.id.btn_advance:
+ startActivity(new Intent(MainActivity.this, AdvanceActivity.class));
+ break;
+ }
+ }
+
+ private void startSimpleItent(int type) {
+ Intent intent = new Intent(this, SimpleActivity.class);
+ intent.putExtra("type", type);
+ startActivity(intent);
+ }
+}
diff --git a/fresco-demo/app/src/main/java/com/yql/fresco/SimpleActivity.java b/fresco-demo/app/src/main/java/com/yql/fresco/SimpleActivity.java
new file mode 100644
index 0000000..51997ca
--- /dev/null
+++ b/fresco-demo/app/src/main/java/com/yql/fresco/SimpleActivity.java
@@ -0,0 +1,225 @@
+package com.yql.fresco;
+
+import android.app.Activity;
+import android.graphics.PointF;
+import android.graphics.drawable.Animatable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.Toast;
+
+import com.facebook.common.util.UriUtil;
+import com.facebook.drawee.backends.pipeline.Fresco;
+import com.facebook.drawee.controller.ControllerListener;
+import com.facebook.drawee.drawable.ScalingUtils;
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.generic.RoundingParams;
+import com.facebook.drawee.interfaces.DraweeController;
+import com.facebook.drawee.view.SimpleDraweeView;
+import com.facebook.imagepipeline.image.ImageInfo;
+
+import javax.annotation.Nullable;
+
+
+public class SimpleActivity extends Activity implements ControllerListener {
+ public static final int TYPE_XML = 0;
+ public static final int TYPE_CODE = 1;
+ private SimpleDraweeView simpleDraweeView;
+ private int type;
+ private static String IMG_URI = "http://img4.imgtn.bdimg.com/it/u=753569864,2606700962&fm=21&gp=0.jpg";
+ private static String GIF_URI = "http://img2.duitang.com/uploads/item/201303/15/20130315134323_PMTrz.gif";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ //这里记得要在setContentView之前初始化
+ Fresco.initialize(this);
+ setContentView(R.layout.activity_simple);
+ type = getIntent().getIntExtra("type", TYPE_XML);
+ simpleDraweeView = (SimpleDraweeView) findViewById(R.id.img);
+
+ loadImg();
+ }
+
+ private void loadImg() {
+ switch (type) {
+ case TYPE_XML:
+ loadImgXml();
+ break;
+ case TYPE_CODE:
+ loadImgCode();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void loadImgXml() {
+ ViewStub viewStub = (ViewStub) findViewById(R.id.viewstub_in_xml_opt);
+ viewStub.inflate();
+ //这里可以设置展示的图片比例,要在xml设置layout_width为一固定值,layout_height设为wrap_content
+// simpleDraweeView.setAspectRatio(1.33f);
+
+ //Supported URIs: http://, https://, file://, content://, asset://, res://
+ //注意:这里是指绝对路径
+ Uri uri = Uri.parse(getUriStr(TypeEnum.RES_JPG));
+ simpleDraweeView.setImageURI(uri);
+ //如果actualImageScaleType选为FOCUS_CROP,则可以指定区域裁剪,(0f,0f)为左上角,(1f,1f)为右上角
+ simpleDraweeView.getHierarchy().setActualImageFocusPoint(new PointF(0.5f, 0f));
+ simpleDraweeView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ //do something
+ Toast.makeText(SimpleActivity.this,
+ "这里是为了触发pressedStateOverlayImage效果,与retry事件不冲突", Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private void loadImgCode() {
+ ViewStub viewStub = (ViewStub) findViewById(R.id.viewstub_in_code_opt);
+ viewStub.inflate();
+
+
+ RoundingParams roundingParams = new RoundingParams();
+ roundingParams.setRoundAsCircle(true);
+ roundingParams.setBorder(R.color.border_color, 10);
+ //RoundingMethod.BITMAP_ONLY不支持failure图片,retry图片和动画(gif),只作用于实际图片和占位图
+ roundingParams.setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY);
+ //覆盖层模式
+// roundingParams.setRoundingMethod(RoundingParams.RoundingMethod.OVERLAY_COLOR);
+// roundingParams.setOverlayColor(getResources().getColor(R.color.corner_color));
+
+
+ //or GenericDraweeHierarchy hierarchy = simpleDraweeView.getHierarchy();
+ GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(getResources());
+ GenericDraweeHierarchy hierarchy = builder.setFadeDuration(1000)
+ //设置实际图片的scaleType
+ .setActualImageScaleType(ScalingUtils.ScaleType.FOCUS_CROP)
+ .setActualImageFocusPoint(new PointF(1f, 1f))
+// .setActualImageColorFilter(new PorterDuffColorFilter(R.color.red, PorterDuff.Mode.DARKEN))
+ //设置占位图drawable和scaleType
+ .setPlaceholderImage(getResources().getDrawable(R.color.placeholder), ScalingUtils.ScaleType.CENTER_CROP)
+ //设置error drawable和scaleType
+ .setFailureImage(getResources().getDrawable(R.color.error), ScalingUtils.ScaleType.CENTER_CROP)
+ //设置重试drawable, 记得在controller下设置setTapToRetryEnabled(true)
+ .setRetryImage(getResources().getDrawable(R.color.retrying))
+ //设置加载条drawable
+ .setProgressBarImage(getResources().getDrawable(R.drawable.drawable_progress))
+ .setOverlay(getResources().getDrawable(R.color.transparent))
+ .setPressedStateOverlay(getResources().getDrawable(R.color.transparent_50))
+ .setRoundingParams(roundingParams)
+ .build();
+ simpleDraweeView.setHierarchy(hierarchy);
+ setDraweeUri(TypeEnum.HTTP_JPG);
+
+ }
+
+ private String getUriStr(TypeEnum type) {
+ String uri = IMG_URI;
+ StringBuilder builder = new StringBuilder();
+ if (type.equals(TypeEnum.HTTP_JPG)) {
+ uri = IMG_URI;
+ } else if (type.equals(TypeEnum.HTTP_GIF)) {
+ uri = GIF_URI;
+ } else if (type.equals(TypeEnum.RES_WEBP)) {
+ uri = builder.append(UriUtil.LOCAL_RESOURCE_SCHEME).append("://").append(getResources().getResourcePackageName(R.raw.local_img_1))
+ .append("/").append(R.raw.local_img_1).toString();
+ } else if (type.equals(TypeEnum.RES_JPG)) {
+ uri = builder.append(UriUtil.LOCAL_RESOURCE_SCHEME).append("://").append(getResources().getResourcePackageName(R.raw.local_img))
+ .append("/").append(R.raw.local_img).toString();
+ }
+ return uri;
+ }
+
+ private void setDraweeUri(TypeEnum type) {
+ DraweeController controller = Fresco.newDraweeControllerBuilder()
+ //tap-to-retry load image
+ .setTapToRetryEnabled(true)
+// .setAutoPlayAnimations(true)是否自动开启gif,webp动画,也可以在ControllerListener下手动启动动画
+ //在构建新的控制器时需要setOldController,这可以防止重新分配内存
+ .setOldController(simpleDraweeView.getController())
+ .setUri(Uri.parse(getUriStr(type)))
+ .setControllerListener(this)
+ .build();
+ simpleDraweeView.setController(controller);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_simple, menu);
+ return type == TYPE_CODE;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_http) {
+ setDraweeUri(TypeEnum.HTTP_JPG);
+ return true;
+ } else if (id == R.id.action_gif) {
+ setDraweeUri(TypeEnum.HTTP_GIF);
+ return true;
+ } else if (id == R.id.action_webp) {
+ setDraweeUri(TypeEnum.RES_WEBP);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+
+ @Override
+ public void onSubmit(String s, Object o) {
+
+ }
+
+ @Override
+ public void onFinalImageSet(String s, @Nullable Object o, @Nullable Animatable animatable) {
+ ImageInfo imageInfo = (ImageInfo) o;
+ if (imageInfo == null) {
+ return;
+ }
+// QualityInfo qualityInfo = imageInfo.getQualityInfo();
+ Log.d("onFinalImageSet", "图片获取完成>>>>" + "大小 " + imageInfo.getWidth() + " x " + imageInfo.getHeight());
+ if (animatable != null) {
+ animatable.start();//启动gif动画
+ }
+
+ }
+
+ @Override
+ public void onIntermediateImageSet(String s, @Nullable Object o) {
+
+ }
+
+ @Override
+ public void onIntermediateImageFailed(String s, Throwable throwable) {
+
+ }
+
+ @Override
+ public void onFailure(String s, Throwable throwable) {
+
+ }
+
+ @Override
+ public void onRelease(String s) {
+
+ }
+
+ public enum TypeEnum {
+ HTTP_JPG, HTTP_GIF, RES_WEBP, RES_JPG
+ }
+}
diff --git a/fresco-demo/app/src/main/res/drawable/drawable_progress.xml b/fresco-demo/app/src/main/res/drawable/drawable_progress.xml
new file mode 100644
index 0000000..b8bd444
--- /dev/null
+++ b/fresco-demo/app/src/main/res/drawable/drawable_progress.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/fresco-demo/app/src/main/res/drawable/loal_exif.jpg b/fresco-demo/app/src/main/res/drawable/loal_exif.jpg
new file mode 100644
index 0000000..535cbd0
Binary files /dev/null and b/fresco-demo/app/src/main/res/drawable/loal_exif.jpg differ
diff --git a/fresco-demo/app/src/main/res/drawable/local_first_330_220.jpg b/fresco-demo/app/src/main/res/drawable/local_first_330_220.jpg
new file mode 100644
index 0000000..c33f86d
Binary files /dev/null and b/fresco-demo/app/src/main/res/drawable/local_first_330_220.jpg differ
diff --git a/fresco-demo/app/src/main/res/drawable/progress_bar.png b/fresco-demo/app/src/main/res/drawable/progress_bar.png
new file mode 100644
index 0000000..f59a1de
Binary files /dev/null and b/fresco-demo/app/src/main/res/drawable/progress_bar.png differ
diff --git a/fresco-demo/app/src/main/res/layout/activity_advance.xml b/fresco-demo/app/src/main/res/layout/activity_advance.xml
new file mode 100644
index 0000000..f9eb282
--- /dev/null
+++ b/fresco-demo/app/src/main/res/layout/activity_advance.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/fresco-demo/app/src/main/res/layout/activity_main.xml b/fresco-demo/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..8dd388c
--- /dev/null
+++ b/fresco-demo/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fresco-demo/app/src/main/res/layout/activity_simple.xml b/fresco-demo/app/src/main/res/layout/activity_simple.xml
new file mode 100644
index 0000000..f2c71f0
--- /dev/null
+++ b/fresco-demo/app/src/main/res/layout/activity_simple.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/fresco-demo/app/src/main/res/layout/opt_in_code.xml b/fresco-demo/app/src/main/res/layout/opt_in_code.xml
new file mode 100644
index 0000000..11e51f6
--- /dev/null
+++ b/fresco-demo/app/src/main/res/layout/opt_in_code.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fresco-demo/app/src/main/res/layout/opt_in_xml.xml b/fresco-demo/app/src/main/res/layout/opt_in_xml.xml
new file mode 100644
index 0000000..4bd9943
--- /dev/null
+++ b/fresco-demo/app/src/main/res/layout/opt_in_xml.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/fresco-demo/app/src/main/res/menu/menu_advance.xml b/fresco-demo/app/src/main/res/menu/menu_advance.xml
new file mode 100644
index 0000000..7328dc7
--- /dev/null
+++ b/fresco-demo/app/src/main/res/menu/menu_advance.xml
@@ -0,0 +1,13 @@
+
diff --git a/fresco-demo/app/src/main/res/menu/menu_main.xml b/fresco-demo/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..87a750e
--- /dev/null
+++ b/fresco-demo/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,5 @@
+
diff --git a/fresco-demo/app/src/main/res/menu/menu_simple.xml b/fresco-demo/app/src/main/res/menu/menu_simple.xml
new file mode 100644
index 0000000..15882ec
--- /dev/null
+++ b/fresco-demo/app/src/main/res/menu/menu_simple.xml
@@ -0,0 +1,9 @@
+
diff --git a/fresco-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/fresco-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/fresco-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/fresco-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/fresco-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/fresco-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/fresco-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/fresco-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/fresco-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/fresco-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/fresco-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/fresco-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/fresco-demo/app/src/main/res/raw/local_img.jpg b/fresco-demo/app/src/main/res/raw/local_img.jpg
new file mode 100644
index 0000000..16777e5
Binary files /dev/null and b/fresco-demo/app/src/main/res/raw/local_img.jpg differ
diff --git a/fresco-demo/app/src/main/res/raw/local_img_1.webp b/fresco-demo/app/src/main/res/raw/local_img_1.webp
new file mode 100644
index 0000000..37dc981
Binary files /dev/null and b/fresco-demo/app/src/main/res/raw/local_img_1.webp differ
diff --git a/fresco-demo/app/src/main/res/values-v21/styles.xml b/fresco-demo/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..dba3c41
--- /dev/null
+++ b/fresco-demo/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/fresco-demo/app/src/main/res/values-w820dp/dimens.xml b/fresco-demo/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/fresco-demo/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/fresco-demo/app/src/main/res/values/colors.xml b/fresco-demo/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..97f96e7
--- /dev/null
+++ b/fresco-demo/app/src/main/res/values/colors.xml
@@ -0,0 +1,12 @@
+
+
+ #e1e4eb
+ #ff9999
+ #008899
+ #0000ff
+ #ff0000
+ #ff5050
+ #505050
+ #00000000
+ #7f000000
+
\ No newline at end of file
diff --git a/fresco-demo/app/src/main/res/values/dimens.xml b/fresco-demo/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/fresco-demo/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/fresco-demo/app/src/main/res/values/strings.xml b/fresco-demo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..25b20a6
--- /dev/null
+++ b/fresco-demo/app/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+
+ fresco demo
+
+ Hello world!
+ Settings
+ SimpleActivity
+
+ simple in xml
+ simple in code
+ advance in code
+ just made in xml
+ buid in the code
+
+ HTTP_JPG
+ HTTP_GIF
+ RES_WEBP
+
+ AdvanceActivity
+ progressiveJpeg
+ low2high
+ thumbPreview
+ firstAvailabel
+ modifyImg
+
diff --git a/fresco-demo/app/src/main/res/values/styles.xml b/fresco-demo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..ff6c9d2
--- /dev/null
+++ b/fresco-demo/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/fresco-demo/build.gradle b/fresco-demo/build.gradle
new file mode 100644
index 0000000..d3ff69d
--- /dev/null
+++ b/fresco-demo/build.gradle
@@ -0,0 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.1.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/fresco-demo/gradle.properties b/fresco-demo/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/fresco-demo/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/fresco-demo/gradle/wrapper/gradle-wrapper.jar b/fresco-demo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/fresco-demo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/fresco-demo/gradle/wrapper/gradle-wrapper.properties b/fresco-demo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/fresco-demo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/fresco-demo/gradlew b/fresco-demo/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/fresco-demo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/fresco-demo/gradlew.bat b/fresco-demo/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/fresco-demo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/git.lnk b/git.lnk
new file mode 100644
index 0000000..f51e5b3
Binary files /dev/null and b/git.lnk differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/.classpath b/listview-animations-demo/ListviewAnimationDemo/.classpath
deleted file mode 100644
index 5176974..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/listview-animations-demo/ListviewAnimationDemo/.gitignore b/listview-animations-demo/ListviewAnimationDemo/.gitignore
new file mode 100644
index 0000000..c6cbe56
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/.gitignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/listview-animations-demo/ListviewAnimationDemo/.project b/listview-animations-demo/ListviewAnimationDemo/.project
deleted file mode 100644
index 5fea0b6..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
- ListviewAnimationDemo
-
-
-
-
-
- com.android.ide.eclipse.adt.ResourceManagerBuilder
-
-
-
-
- com.android.ide.eclipse.adt.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- com.android.ide.eclipse.adt.ApkBuilder
-
-
-
-
-
- com.android.ide.eclipse.adt.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/.gitignore b/listview-animations-demo/ListviewAnimationDemo/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/build.gradle b/listview-animations-demo/ListviewAnimationDemo/app/build.gradle
new file mode 100644
index 0000000..70b1a6f
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.1"
+
+ defaultConfig {
+ applicationId "com.example.listviewanimationdemo"
+ minSdkVersion 9
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ testCompile 'junit:junit:4.12'
+ compile 'com.android.support:appcompat-v7:23.0.1'
+ compile 'com.nineoldandroids:library:2.4.0'
+ compile 'com.nhaarman.listviewanimations:lib-core:3.1.0@aar'
+ compile 'com.nhaarman.listviewanimations:lib-manipulation:3.1.0@aar'
+ compile 'com.nhaarman.listviewanimations:lib-core-slh:3.1.0@aar'
+ compile 'se.emilsjolander:stickylistheaders:2.6.0'
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/proguard-rules.pro b/listview-animations-demo/ListviewAnimationDemo/app/proguard-rules.pro
new file mode 100644
index 0000000..48a27cd
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:\adt-bundle\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/androidTest/java/com/example/listviewanimationdemo/ApplicationTest.java b/listview-animations-demo/ListviewAnimationDemo/app/src/androidTest/java/com/example/listviewanimationdemo/ApplicationTest.java
new file mode 100644
index 0000000..eab190e
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/androidTest/java/com/example/listviewanimationdemo/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.example.listviewanimationdemo;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationDemo/AndroidManifest.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/AndroidManifest.xml
similarity index 61%
rename from listview-animations-demo/ListviewAnimationDemo/AndroidManifest.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/AndroidManifest.xml
index 81855cd..ca874f0 100644
--- a/listview-animations-demo/ListviewAnimationDemo/AndroidManifest.xml
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/AndroidManifest.xml
@@ -1,20 +1,15 @@
-
-
+ package="com.example.listviewanimationdemo">
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ android:screenOrientation="portrait">
@@ -23,40 +18,31 @@
-
+ android:screenOrientation="portrait">
-
+ android:screenOrientation="portrait">
-
+ android:screenOrientation="portrait">
-
+ android:screenOrientation="portrait">
-
+ android:screenOrientation="portrait">
-
+ android:screenOrientation="portrait">
-
+ android:screenOrientation="portrait">
-
+ android:screenOrientation="portrait">
-
+ android:screenOrientation="portrait">
-
\ No newline at end of file
+
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/HomeActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/HomeActivity.java
new file mode 100644
index 0000000..129e92b
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/HomeActivity.java
@@ -0,0 +1,88 @@
+package com.example.listviewanimationdemo;
+
+import com.example.listviewanimationdemo.itemanimation.ItemAnimationMainActivity;
+import com.example.listviewanimationdemo.itemmanipulation.AnimateAdditionActivity;
+import com.example.listviewanimationdemo.itemmanipulation.AnimateDismissActivity;
+import com.example.listviewanimationdemo.itemmanipulation.DragAndDropActivity;
+import com.example.listviewanimationdemo.itemmanipulation.ExpandableListItemActivity;
+import com.example.listviewanimationdemo.itemmanipulation.SwipeToDismissActivity;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Toast;
+
+public class HomeActivity extends FragmentActivity implements OnClickListener {
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setTitle("ListAnimation动画示例");
+ setContentView(R.layout.activity_home);
+ initView();
+ }
+
+ void initView() {
+ findViewById(R.id.home_item_animation_btn).setOnClickListener(this);
+ findViewById(R.id.home_swipe_to_dismiss_btn).setOnClickListener(this);
+ findViewById(R.id.home_item_drag_btn).setOnClickListener(this);
+ findViewById(R.id.home_multiple_item_dismiss_btn).setOnClickListener(
+ this);
+ findViewById(R.id.home_item_expend_btn).setOnClickListener(this);
+ findViewById(R.id.home_item_add_btn).setOnClickListener(this);
+ findViewById(R.id.home_item_go_github).setOnClickListener(this);
+ findViewById(R.id.home_item_go_la_github).setOnClickListener(this);
+ findViewById(R.id.home_item_go_demo_author_blog).setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ switch (id) {
+ case R.id.home_item_animation_btn:
+ ItemAnimationMainActivity.actionToItemAnimationMain(this);
+ break;
+ case R.id.home_swipe_to_dismiss_btn:
+ SwipeToDismissActivity.actionToSwipeDismiss(this);
+ break;
+ case R.id.home_item_drag_btn:
+ DragAndDropActivity.actionToDragAndDrop(this);
+ break;
+ case R.id.home_multiple_item_dismiss_btn:
+ AnimateDismissActivity.actionToAnimateDismiss(this);
+ break;
+ case R.id.home_item_expend_btn:
+ ExpandableListItemActivity.actionToExpandableList(this);
+ break;
+ case R.id.home_item_add_btn:
+ AnimateAdditionActivity.actionToItemAddition(this);
+ break;
+
+ case R.id.home_item_go_github:
+ Toast.makeText(this, "正在转向demo项目主页...", Toast.LENGTH_SHORT).show();
+ openBrower("https://github.com/android-cn/android-open-project-demo");
+ break;
+ case R.id.home_item_go_la_github:
+ Toast.makeText(this, "正在转向ListviewAnimation项目主页...",
+ Toast.LENGTH_SHORT).show();
+ openBrower("https://github.com/nhaarman/ListViewAnimations");
+ break;
+ case R.id.home_item_go_demo_author_blog:
+ Toast.makeText(this, "正在转向Demo作者博客主页...",
+ Toast.LENGTH_SHORT).show();
+ openBrower("http://waylife.github.io");
+ break;
+ default:
+ break;
+ }
+ }
+
+ public void openBrower(String url) {
+// Uri uri = Uri.parse(url);
+// Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+// intent.setPackage("com.android.browser");
+// startActivity(intent);
+ }
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/base/BaseListActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/base/BaseListActivity.java
new file mode 100644
index 0000000..4cdf72a
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/base/BaseListActivity.java
@@ -0,0 +1,80 @@
+package com.example.listviewanimationdemo.base;
+
+import java.util.ArrayList;
+
+
+import com.example.listviewanimationdemo.R;
+import com.nhaarman.listviewanimations.ArrayAdapter;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class BaseListActivity extends FragmentActivity {
+
+ private ListView mListView;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_baselist);
+ mListView = (ListView) findViewById(R.id.activity_baselist_listview);
+ mListView.setDivider(null);
+ }
+
+ public ListView getListView() {
+ return mListView;
+ }
+
+ protected ArrayAdapter createListAdapter() {
+ return new MyListAdapter(this, getItems());
+ }
+
+ public static ArrayList getItems() {
+ ArrayList items = new ArrayList();
+ for (int i = 0; i < 1000; i++) {
+ items.add(i);
+ }
+ return items;
+ }
+
+ private static class MyListAdapter extends ArrayAdapter {
+
+ private final Context mContext;
+
+ public MyListAdapter(final Context context,
+ final ArrayList items) {
+ super(items);
+ mContext = context;
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return getItem(position).hashCode();
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(mContext).inflate(
+ R.layout.item_list_row, parent, false);
+ }
+ TextView tv = (TextView) convertView.findViewById(R.id.id_text_view);
+
+ tv.setText("第 " + getItem(position) + "行");
+ return convertView;
+ }
+ }
+
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/GoogleCardsActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/GoogleCardsActivity.java
new file mode 100644
index 0000000..3c82901
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/GoogleCardsActivity.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2013 Niek Haarman
+ *
+ * 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 com.example.listviewanimationdemo.itemanimation;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.LruCache;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.example.listviewanimationdemo.R;
+import com.nhaarman.listviewanimations.ArrayAdapter;
+import com.nhaarman.listviewanimations.appearance.simple.SwingBottomInAnimationAdapter;
+import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.OnDismissCallback;
+import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.SwipeDismissAdapter;
+
+public class GoogleCardsActivity extends FragmentActivity implements OnDismissCallback {
+
+ private GoogleCardsAdapter mGoogleCardsAdapter;
+
+ public static void actionToGoogleCard(Context context) {
+ Intent intent = new Intent(context, GoogleCardsActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle("Google Card");
+ setContentView(R.layout.activity_googlecards);
+
+ ListView listView = (ListView) findViewById(R.id.activity_googlecards_listview);
+
+ mGoogleCardsAdapter = new GoogleCardsAdapter(this);
+ SwingBottomInAnimationAdapter swingBottomInAnimationAdapter = new SwingBottomInAnimationAdapter(new SwipeDismissAdapter(mGoogleCardsAdapter, this));
+ swingBottomInAnimationAdapter.setAbsListView(listView);
+
+ listView.setAdapter(swingBottomInAnimationAdapter);
+
+ mGoogleCardsAdapter.addAll(getItems());
+ }
+
+ private ArrayList getItems() {
+ ArrayList items = new ArrayList();
+ for (int i = 0; i < 100; i++) {
+ items.add(i);
+ }
+ return items;
+ }
+
+ @Override
+ public void onDismiss(ViewGroup listView, int[] reverseSortedPositions) {
+ for (int position : reverseSortedPositions) {
+ mGoogleCardsAdapter.remove(position);
+ }
+ }
+
+ private static class GoogleCardsAdapter extends ArrayAdapter {
+
+ private final Context mContext;
+ private final LruCache mMemoryCache;
+
+ public GoogleCardsAdapter(final Context context) {
+ mContext = context;
+
+ final int cacheSize = (int) (Runtime.getRuntime().maxMemory() / 1024);
+ mMemoryCache = new LruCache(cacheSize) {
+ @Override
+ protected int sizeOf(final Integer key, final Bitmap bitmap) {
+ // The cache size will be measured in kilobytes rather than
+ // number of items.
+ return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
+ }
+ };
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ ViewHolder viewHolder;
+ View view = convertView;
+ if (view == null) {
+ view = LayoutInflater.from(mContext).inflate(R.layout.activity_googlecards_card, parent, false);
+
+ viewHolder = new ViewHolder();
+ viewHolder.textView = (TextView) view.findViewById(R.id.activity_googlecards_card_textview);
+ view.setTag(viewHolder);
+
+ viewHolder.imageView = (ImageView) view.findViewById(R.id.activity_googlecards_card_imageview);
+ } else {
+ viewHolder = (ViewHolder) view.getTag();
+ }
+
+ viewHolder.textView.setText("这是第 " + (getItem(position) + 1) + "个卡片");
+ setImageView(viewHolder, position);
+
+ return view;
+ }
+
+ private void setImageView(final ViewHolder viewHolder, final int position) {
+ int imageResId;
+ switch (getItem(position) % 5) {
+ case 0:
+ imageResId = R.drawable.img_nature1;
+ break;
+ case 1:
+ imageResId = R.drawable.img_nature2;
+ break;
+ case 2:
+ imageResId = R.drawable.img_nature3;
+ break;
+ case 3:
+ imageResId = R.drawable.img_nature4;
+ break;
+ default:
+ imageResId = R.drawable.img_nature5;
+ }
+
+ Bitmap bitmap = getBitmapFromMemCache(imageResId);
+ if (bitmap == null) {
+ bitmap = BitmapFactory.decodeResource(mContext.getResources(), imageResId);
+ addBitmapToMemoryCache(imageResId, bitmap);
+ }
+ viewHolder.imageView.setImageBitmap(bitmap);
+ }
+
+ private void addBitmapToMemoryCache(final int key, final Bitmap bitmap) {
+ if (getBitmapFromMemCache(key) == null) {
+ mMemoryCache.put(key, bitmap);
+ }
+ }
+
+ private Bitmap getBitmapFromMemCache(final int key) {
+ return mMemoryCache.get(key);
+ }
+
+ private static class ViewHolder {
+ TextView textView;
+ ImageView imageView;
+ }
+ }
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/GridViewActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/GridViewActivity.java
new file mode 100644
index 0000000..3cdd876
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/GridViewActivity.java
@@ -0,0 +1,140 @@
+package com.example.listviewanimationdemo.itemanimation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.LruCache;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridView;
+import android.widget.ImageView;
+
+import com.example.listviewanimationdemo.R;
+import com.nhaarman.listviewanimations.ArrayAdapter;
+import com.nhaarman.listviewanimations.appearance.simple.SwingBottomInAnimationAdapter;
+
+public class GridViewActivity extends FragmentActivity {
+
+ public static void actionToGridView(Context context) {
+ Intent intent = new Intent(context, GridViewActivity.class);
+ context.startActivity(intent);
+ }
+
+ @SuppressLint("InlinedApi")
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ getWindow()
+ .addFlags(
+ android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ }
+ super.onCreate(savedInstanceState);
+ setTitle("Gridview动画");
+ setContentView(R.layout.activity_gridview);
+
+ GridView gridView = (GridView) findViewById(R.id.activity_gridview_gv);
+ SwingBottomInAnimationAdapter swingBottomInAnimationAdapter = new SwingBottomInAnimationAdapter(
+ new MyAdapter(this, getItems()));
+ swingBottomInAnimationAdapter.setAbsListView(gridView);
+ gridView.setAdapter(swingBottomInAnimationAdapter);
+
+ }
+
+ private ArrayList getItems() {
+ ArrayList items = new ArrayList();
+ for (int i = 0; i < 100; i++) {
+ items.add(i);
+ }
+ return items;
+ }
+
+ private static class MyAdapter extends ArrayAdapter {
+
+ private final Context mContext;
+ private final LruCache mMemoryCache;
+
+ public MyAdapter(final Context context, final List list) {
+ super(list);
+ mContext = context;
+
+ final int cacheSize = (int) (Runtime.getRuntime().maxMemory() / 1024);
+ mMemoryCache = new LruCache(cacheSize) {
+ @Override
+ protected int sizeOf(final Integer key, final Bitmap bitmap) {
+ // The cache size will be measured in kilobytes rather than
+ // number of items.
+ return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
+ }
+ };
+ }
+
+ @Override
+ public View getView(final int position, final View convertView,
+ final ViewGroup viewGroup) {
+ ImageView imageView = (ImageView) convertView;
+
+ if (imageView == null) {
+ imageView = new ImageView(mContext);
+ imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ }
+
+ int imageResId;
+ switch (getItem(position) % 5) {
+ case 0:
+ imageResId = R.drawable.img_nature1;
+ break;
+ case 1:
+ imageResId = R.drawable.img_nature2;
+ break;
+ case 2:
+ imageResId = R.drawable.img_nature3;
+ break;
+ case 3:
+ imageResId = R.drawable.img_nature4;
+ break;
+ default:
+ imageResId = R.drawable.img_nature5;
+ }
+
+ Bitmap bitmap = getBitmapFromMemCache(imageResId);
+ if (bitmap == null) {
+ bitmap = BitmapFactory.decodeResource(mContext.getResources(),
+ imageResId);
+ addBitmapToMemoryCache(imageResId, bitmap);
+ }
+ imageView.setImageBitmap(bitmap);
+
+ return imageView;
+ }
+
+ private void addBitmapToMemoryCache(final int key, final Bitmap bitmap) {
+ if (getBitmapFromMemCache(key) == null) {
+ mMemoryCache.put(key, bitmap);
+ }
+ }
+
+ private Bitmap getBitmapFromMemCache(final int key) {
+ return mMemoryCache.get(key);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/ItemAnimationActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/ItemAnimationActivity.java
new file mode 100644
index 0000000..89468c2
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/ItemAnimationActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2013 Niek Haarman
+ *
+ * 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 com.example.listviewanimationdemo.itemanimation;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import com.example.listviewanimationdemo.R;
+import com.example.listviewanimationdemo.base.BaseListActivity;
+import com.nhaarman.listviewanimations.ArrayAdapter;
+import com.nhaarman.listviewanimations.appearance.AnimationAdapter;
+import com.nhaarman.listviewanimations.appearance.simple.AlphaInAnimationAdapter;
+import com.nhaarman.listviewanimations.appearance.simple.ScaleInAnimationAdapter;
+import com.nhaarman.listviewanimations.appearance.simple.SwingBottomInAnimationAdapter;
+import com.nhaarman.listviewanimations.appearance.simple.SwingLeftInAnimationAdapter;
+import com.nhaarman.listviewanimations.appearance.simple.SwingRightInAnimationAdapter;
+
+
+import java.util.ArrayList;
+
+public class ItemAnimationActivity extends BaseListActivity {
+
+ private BaseAdapter mAdapter;
+
+ public static void actionToItemAnimation(Context context) {
+ Intent intent = new Intent(context, ItemAnimationActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle("item动画");
+ mAdapter = new MyAdapter(this, getItems());
+ setAlphaAdapter();
+ }
+
+ private void setAlphaAdapter() {
+ AnimationAdapter animAdapter = new AlphaInAnimationAdapter(mAdapter);
+ animAdapter.setAbsListView(getListView());
+ getListView().setAdapter(animAdapter);
+ }
+
+ private void setLeftAdapter() {
+ AnimationAdapter animAdapter = new SwingLeftInAnimationAdapter(mAdapter);
+ animAdapter.setAbsListView(getListView());
+ getListView().setAdapter(animAdapter);
+ }
+
+ private void setRightAdapter() {
+ AnimationAdapter animAdapter = new SwingRightInAnimationAdapter(
+ mAdapter);
+ animAdapter.setAbsListView(getListView());
+ getListView().setAdapter(animAdapter);
+ }
+
+ private void setBottomAdapter() {
+ AnimationAdapter animAdapter = new SwingBottomInAnimationAdapter(
+ mAdapter);
+ animAdapter.setAbsListView(getListView());
+ getListView().setAdapter(animAdapter);
+ }
+
+ private void setBottomRightAdapter() {
+ AnimationAdapter animAdapter = new SwingBottomInAnimationAdapter(
+ new SwingRightInAnimationAdapter(mAdapter));
+ animAdapter.setAbsListView(getListView());
+ getListView().setAdapter(animAdapter);
+ }
+
+ private void setScaleAdapter() {
+ AnimationAdapter animAdapter = new ScaleInAnimationAdapter(mAdapter);
+ animAdapter.setAbsListView(getListView());
+ getListView().setAdapter(animAdapter);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_item_animation, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ int id = item.getItemId();
+ switch (id) {
+ case R.id.menu_item_animation_alpha:
+ setAlphaAdapter();
+ break;
+ case R.id.menu_item_animation_left:
+ setLeftAdapter();
+ break;
+ case R.id.menu_item_animation_right:
+ setRightAdapter();
+ break;
+ case R.id.menu_item_animation_bottom:
+ setBottomAdapter();
+ break;
+ case R.id.menu_item_animation_bottom_right:
+ setBottomRightAdapter();
+ break;
+ case R.id.menu_item_animation_scale:
+ setScaleAdapter();
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ /* Non-ListViewAnimations related stuff below */
+
+ class MyAdapter extends ArrayAdapter {
+
+ private final Context mContext;
+
+ public MyAdapter(final Context context, final ArrayList items) {
+ super(items);
+ mContext = context;
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return getItem(position).hashCode();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+
+ if (convertView == null) {
+ convertView = LayoutInflater.from(mContext).inflate(
+ R.layout.item_list_row, parent, false);
+ }
+ TextView tv = (TextView) convertView.findViewById(R.id.id_text_view);
+ tv.setText("******第 " + getItem(position) + "行******");
+ return convertView;
+ }
+ }
+}
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/ItemAnimationMainActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/ItemAnimationMainActivity.java
new file mode 100644
index 0000000..34ee606
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemanimation/ItemAnimationMainActivity.java
@@ -0,0 +1,49 @@
+package com.example.listviewanimationdemo.itemanimation;
+
+import com.example.listviewanimationdemo.R;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+public class ItemAnimationMainActivity extends FragmentActivity implements
+ OnClickListener {
+
+ public static void actionToItemAnimationMain(Context context) {
+ Intent intent = new Intent(context, ItemAnimationMainActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setTitle("item动画主页");
+ setContentView(R.layout.activity_item_animation_main);
+ findViewById(R.id.item_animation_main_1).setOnClickListener(this);
+ findViewById(R.id.item_animation_main_google_card).setOnClickListener(
+ this);
+ findViewById(R.id.item_animation_main_gridview)
+ .setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ switch (id) {
+ case R.id.item_animation_main_1:
+ ItemAnimationActivity.actionToItemAnimation(this);
+ break;
+ case R.id.item_animation_main_google_card:
+ GoogleCardsActivity.actionToGoogleCard(this);
+ break;
+ case R.id.item_animation_main_gridview:
+ GridViewActivity.actionToGridView(this);
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/AnimateAdditionActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/AnimateAdditionActivity.java
new file mode 100644
index 0000000..c1caf79
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/AnimateAdditionActivity.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 Niek Haarman
+ *
+ * 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 com.example.listviewanimationdemo.itemmanipulation;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.listviewanimationdemo.R;
+import com.example.listviewanimationdemo.base.BaseListActivity;
+import com.nhaarman.listviewanimations.ArrayAdapter;
+import com.nhaarman.listviewanimations.itemmanipulation.animateaddition.AnimateAdditionAdapter;
+
+import java.util.ArrayList;
+
+public class AnimateAdditionActivity extends BaseListActivity implements
+ AdapterView.OnItemClickListener {
+
+ private int mAddedItemNumber;
+ private AnimateAdditionAdapter mAnimateAdditionAdapter;
+
+ public static void actionToItemAddition(Context context) {
+ Intent intent = new Intent(context, AnimateAdditionActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle("增加item");
+ MyAdapter myAdapter = new MyAdapter(this, getStringItems());
+
+ mAnimateAdditionAdapter = new AnimateAdditionAdapter(myAdapter);
+ mAnimateAdditionAdapter.setListView(getListView());
+
+ getListView().setAdapter(mAnimateAdditionAdapter);
+ getListView().setOnItemClickListener(this);
+
+ Toast.makeText(this, "点击某一项来增加一项", Toast.LENGTH_LONG).show();
+ }
+
+ private static ArrayList getStringItems() {
+ ArrayList items = new ArrayList();
+ for (int i = 0; i < 1000; i++) {
+ items.add(">>>第 " + i + "行===");
+ }
+ return items;
+ }
+
+ @Override
+ public void onItemClick(final AdapterView> parent, final View view,
+ final int position, final long id) {
+ mAnimateAdditionAdapter.insert(position, "***新增加的item: "
+ + mAddedItemNumber);
+ mAddedItemNumber++;
+ }
+
+ private static class MyAdapter extends ArrayAdapter {
+
+ private final Context mContext;
+
+ public MyAdapter(final Context context, final ArrayList items) {
+ super(items);
+ mContext = context;
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return getItem(position).hashCode();
+ }
+
+ @Override
+ public View getView(int position, View convertView,
+ ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(mContext).inflate(
+ R.layout.item_list_row, parent, false);
+ }
+ TextView tv = (TextView) convertView.findViewById(R.id.id_text_view);
+ tv.setText(getItem(position));
+ return convertView;
+ }
+ }
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/AnimateDismissActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/AnimateDismissActivity.java
similarity index 75%
rename from listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/AnimateDismissActivity.java
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/AnimateDismissActivity.java
index c7d6d75..4be1895 100644
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/AnimateDismissActivity.java
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/AnimateDismissActivity.java
@@ -35,8 +35,9 @@
import com.example.listviewanimationdemo.R.layout;
import com.example.listviewanimationdemo.base.BaseListActivity;
import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.AnimateDismissAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.OnDismissCallback;
+import com.nhaarman.listviewanimations.appearance.simple.AlphaInAnimationAdapter;
+import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.OnDismissCallback;
+import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.SwipeDismissAdapter;
import java.util.ArrayList;
import java.util.List;
@@ -47,31 +48,35 @@ public class AnimateDismissActivity extends FragmentActivity {
private MyListAdapter mAdapter;
- public static void actionToAnimateDismiss(Context context) {
- Intent intent = new Intent(context, AnimateDismissActivity.class);
- context.startActivity(intent);
- }
-
+ public static void actionToAnimateDismiss(Context context) {
+ Intent intent = new Intent(context, AnimateDismissActivity.class);
+ context.startActivity(intent);
+ }
+
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("多个item删除");
- setContentView(R.layout.activity_animateremoval);
+ setContentView(layout.activity_animateremoval);
mSelectedPositions = new ArrayList();
- ListView listView = (ListView) findViewById(R.id.activity_animateremoval_listview);
+ ListView listView = (ListView) findViewById(id.activity_animateremoval_listview);
mAdapter = new MyListAdapter(BaseListActivity.getItems());
- final AnimateDismissAdapter animateDismissAdapter = new AnimateDismissAdapter(mAdapter, new MyOnDismissCallback());
+ AlphaInAnimationAdapter animationAdapter = new AlphaInAnimationAdapter(mAdapter);
+ final SwipeDismissAdapter animateDismissAdapter = new SwipeDismissAdapter(animationAdapter, new MyOnDismissCallback());
animateDismissAdapter.setAbsListView(listView);
listView.setAdapter(animateDismissAdapter);
- Button button = (Button) findViewById(R.id.activity_animateremoval_button);
+ Button button = (Button) findViewById(id.activity_animateremoval_button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
- animateDismissAdapter.animateDismiss(mSelectedPositions);
+
+ for (int position : mSelectedPositions) {
+ animateDismissAdapter.dismiss(position);
+ }
mSelectedPositions.clear();
}
});
@@ -92,9 +97,8 @@ public void onItemClick(final AdapterView> parent, final View view, final int
}
private class MyOnDismissCallback implements OnDismissCallback {
-
@Override
- public void onDismiss(final AbsListView listView, final int[] reverseSortedPositions) {
+ public void onDismiss(ViewGroup listView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
mAdapter.remove(position);
}
@@ -111,7 +115,7 @@ public MyListAdapter(final ArrayList items) {
public View getView(final int position, final View convertView, final ViewGroup parent) {
CheckedTextView tv = (CheckedTextView) convertView;
if (tv == null) {
- tv = (CheckedTextView) LayoutInflater.from(AnimateDismissActivity.this).inflate(R.layout.item_animateremoval_row, parent, false);
+ tv = (CheckedTextView) LayoutInflater.from(AnimateDismissActivity.this).inflate(layout.item_animateremoval_row, parent, false);
}
tv.setText(String.valueOf(getItem(position)));
tv.setChecked(mSelectedPositions.contains(position));
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/DragAndDropActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/DragAndDropActivity.java
new file mode 100644
index 0000000..2472313
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/DragAndDropActivity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 Niek Haarman
+ *
+ * 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 com.example.listviewanimationdemo.itemmanipulation;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.listviewanimationdemo.R;
+import com.example.listviewanimationdemo.R.id;
+import com.example.listviewanimationdemo.R.layout;
+import com.example.listviewanimationdemo.base.BaseListActivity;
+import com.nhaarman.listviewanimations.ArrayAdapter;
+import com.nhaarman.listviewanimations.appearance.simple.AlphaInAnimationAdapter;
+import com.nhaarman.listviewanimations.itemmanipulation.DynamicListView;
+import com.nhaarman.listviewanimations.itemmanipulation.dragdrop.OnItemMovedListener;
+import com.nhaarman.listviewanimations.itemmanipulation.dragdrop.TouchViewDraggableManager;
+
+public class DragAndDropActivity extends BaseListActivity {
+
+ public static void actionToDragAndDrop(Context context) {
+ Intent intent = new Intent(context, DragAndDropActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle("item拖动");
+ setContentView(R.layout.activity_draganddrop);
+
+ DynamicListView listView = (DynamicListView) findViewById(id.activity_draganddrop_listview);
+
+ TextView headerView = new TextView(this);
+ headerView.setText("这是头部");
+ headerView.setTextColor(Color.RED);
+ headerView.setGravity(Gravity.CENTER);
+ listView.addHeaderView(headerView);
+
+ final ArrayAdapter adapter = createListAdapter();
+ AlphaInAnimationAdapter animAdapter = new AlphaInAnimationAdapter(adapter);
+ animAdapter.setAbsListView(listView);
+ listView.setAdapter(animAdapter);
+
+ listView.enableDragAndDrop();
+ listView.setDraggableManager(new TouchViewDraggableManager(R.id.id_grip_view));
+ listView.setOnItemMovedListener(new OnItemMovedListener() {
+ @Override
+ public void onItemMoved(int originalPosition, int newPosition) {
+ Toast.makeText(getBaseContext(), originalPosition + "行与" + newPosition
+ + "行交换", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ listView.setOnItemLongClickListener(new MyOnItemLongClickListener(listView));
+
+ Toast.makeText(this, "长按item开始拖拽", Toast.LENGTH_LONG).show();
+
+ }
+
+ private class MyOnItemLongClickListener implements AdapterView.OnItemLongClickListener {
+ private DynamicListView listView;
+
+ public MyOnItemLongClickListener(DynamicListView listView) {
+ this.listView = listView;
+ }
+
+ @Override
+ public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
+ if (listView != null) {
+ listView.startDragging(position - listView.getHeaderViewsCount());
+ }
+ return true;
+ }
+ }
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/ExpandableListItemActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/ExpandableListItemActivity.java
similarity index 84%
rename from listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/ExpandableListItemActivity.java
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/ExpandableListItemActivity.java
index 09d4463..71472fd 100644
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/ExpandableListItemActivity.java
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/ExpandableListItemActivity.java
@@ -26,6 +26,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
@@ -35,8 +36,8 @@
import com.example.listviewanimationdemo.R.layout;
import com.example.listviewanimationdemo.R.menu;
import com.example.listviewanimationdemo.base.BaseListActivity;
-import com.nhaarman.listviewanimations.itemmanipulation.ExpandableListItemAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.AlphaInAnimationAdapter;
+import com.nhaarman.listviewanimations.appearance.simple.AlphaInAnimationAdapter;
+import com.nhaarman.listviewanimations.itemmanipulation.expandablelistitem.ExpandableListItemAdapter;
import java.util.List;
@@ -45,11 +46,11 @@ public class ExpandableListItemActivity extends BaseListActivity {
private MyExpandableListItemAdapter mExpandableListItemAdapter;
private boolean mLimited;
- public static void actionToExpandableList(Context context) {
- Intent intent = new Intent(context, ExpandableListItemActivity.class);
- context.startActivity(intent);
- }
-
+ public static void actionToExpandableList(Context context) {
+ Intent intent = new Intent(context, ExpandableListItemActivity.class);
+ context.startActivity(intent);
+ }
+
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -57,7 +58,6 @@ protected void onCreate(final Bundle savedInstanceState) {
mExpandableListItemAdapter = new MyExpandableListItemAdapter(this, getItems());
AlphaInAnimationAdapter alphaInAnimationAdapter = new AlphaInAnimationAdapter(mExpandableListItemAdapter);
alphaInAnimationAdapter.setAbsListView(getListView());
- alphaInAnimationAdapter.setInitialDelayMillis(500);
getListView().setAdapter(alphaInAnimationAdapter);
Toast.makeText(this, "点击卡片进行展开收缩操作", Toast.LENGTH_LONG).show();
@@ -73,7 +73,7 @@ private static class MyExpandableListItemAdapter extends ExpandableListItemAdapt
* items == null.
*/
private MyExpandableListItemAdapter(final Context context, final List items) {
- super(context, R.layout.item_expandablelistitem_card, R.id.activity_expandablelistitem_card_title, R.id.activity_expandablelistitem_card_content, items);
+ super(context, layout.item_expandablelistitem_card, id.activity_expandablelistitem_card_title, id.activity_expandablelistitem_card_content, items);
mContext = context;
final int cacheSize = (int) (Runtime.getRuntime().maxMemory() / 1024);
@@ -93,7 +93,7 @@ public View getTitleView(final int position, final View convertView, final ViewG
if (tv == null) {
tv = new TextView(mContext);
}
- tv.setText("卡片"+position);
+ tv.setText("卡片" + position);
return tv;
}
@@ -108,19 +108,19 @@ public View getContentView(final int position, final View convertView, final Vie
int imageResId;
switch (getItem(position) % 5) {
case 0:
- imageResId = R.drawable.img_nature1;
+ imageResId = drawable.img_nature1;
break;
case 1:
- imageResId = R.drawable.img_nature2;
+ imageResId = drawable.img_nature2;
break;
case 2:
- imageResId = R.drawable.img_nature3;
+ imageResId = drawable.img_nature3;
break;
case 3:
- imageResId = R.drawable.img_nature4;
+ imageResId = drawable.img_nature4;
break;
default:
- imageResId = R.drawable.img_nature5;
+ imageResId = drawable.img_nature5;
}
Bitmap bitmap = getBitmapFromMemCache(imageResId);
@@ -153,7 +153,7 @@ public boolean onCreateOptionsMenu(final Menu menu) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.menu_expandable_limit:
+ case id.menu_expandable_limit:
mLimited = !mLimited;
item.setChecked(mLimited);
mExpandableListItemAdapter.setLimit(mLimited ? 2 : 0);
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/SwipeToDismissActivity.java b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/SwipeToDismissActivity.java
new file mode 100644
index 0000000..35b024b
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/java/com/example/listviewanimationdemo/itemmanipulation/SwipeToDismissActivity.java
@@ -0,0 +1,82 @@
+package com.example.listviewanimationdemo.itemmanipulation;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+
+import com.example.listviewanimationdemo.R;
+import com.example.listviewanimationdemo.base.BaseListActivity;
+import com.nhaarman.listviewanimations.ArrayAdapter;
+import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.OnDismissCallback;
+import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.SwipeDismissAdapter;
+
+public class SwipeToDismissActivity extends BaseListActivity implements OnDismissCallback {
+
+ public static void actionToSwipeDismiss(Context context) {
+ Intent intent = new Intent(context, SwipeToDismissActivity.class);
+ context.startActivity(intent);
+ }
+
+ private ArrayAdapter mAdapter;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle("item滑动删除");
+ mAdapter = createListAdapter();
+ setSwipeDismissAdapter();
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_swipe_to_dismiss, menu);
+ return true;
+ }
+
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ int id = item.getItemId();
+ switch (id) {
+ case R.id.swipe_menu_main:
+ setSwipeDismissAdapter();
+ break;
+ case R.id.swipe_menu_with_undo:
+ setContextualUndoAdapter();
+ break;
+ case R.id.swipe_menu_with_undo_2:
+ setContextualUndoWithTimedDeleteAdapter();
+ break;
+ case R.id.swipe_menu_with_undo_3:
+ setContextualUndoWithTimedDeleteAndCountDownAdapter();
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+
+ private void setSwipeDismissAdapter() {
+ SwipeDismissAdapter adapter = new SwipeDismissAdapter(mAdapter, this);
+ adapter.setAbsListView(getListView());
+ getListView().setAdapter(adapter);
+ }
+
+ private void setContextualUndoAdapter() {
+ }
+
+ private void setContextualUndoWithTimedDeleteAdapter() {
+ }
+
+ private void setContextualUndoWithTimedDeleteAndCountDownAdapter() {
+
+ }
+
+ @Override
+ public void onDismiss(ViewGroup listView, int[] reverseSortedPositions) {
+
+ }
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-hdpi/ic_launcher.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..288b665
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/drawable-hdpi/ic_undo.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-hdpi/ic_undo.png
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/drawable-hdpi/ic_undo.png
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-hdpi/ic_undo.png
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-mdpi/ic_launcher.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..6ae570b
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-xhdpi/ic_launcher.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d4fb7cd
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..85a6081
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/drawable/card_background_white.9.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/card_background_white.9.png
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/drawable/card_background_white.9.png
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/card_background_white.9.png
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature1.jpg b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature1.jpg
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature1.jpg
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature1.jpg
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature2.jpg b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature2.jpg
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature2.jpg
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature2.jpg
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature3.jpg b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature3.jpg
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature3.jpg
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature3.jpg
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature4.jpg b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature4.jpg
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature4.jpg
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature4.jpg
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature5.jpg b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature5.jpg
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/drawable/img_nature5.jpg
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/drawable/img_nature5.jpg
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/layout/activity_animateremoval.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/layout/activity_animateremoval.xml
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/layout/activity_animateremoval.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/layout/activity_animateremoval.xml
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/layout/activity_baselist.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/layout/activity_baselist.xml
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/layout/activity_baselist.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/layout/activity_baselist.xml
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/layout/activity_draganddrop.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/layout/activity_draganddrop.xml
similarity index 64%
rename from listview-animations-demo/ListviewAnimationDemo/res/layout/activity_draganddrop.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/layout/activity_draganddrop.xml
index 68145de..e9fcba4 100644
--- a/listview-animations-demo/ListviewAnimationDemo/res/layout/activity_draganddrop.xml
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/layout/activity_draganddrop.xml
@@ -1,9 +1,9 @@
-
+
-
+ android:layout_height="match_parent">
+ android:padding="5dp">
-
+ android:text="2.1滑动删除">
+ android:text="2.2item拖动">
+ android:text="2.3多个item删除">
+ android:text="2.4列表展开">
+ android:text="2.5item增加">
+
+
+
+
+
+
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/layout/item_undo_row.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/layout/item_undo_row.xml
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/layout/item_undo_row.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/layout/item_undo_row.xml
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/menu/menu_expandablelistitem.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/menu/menu_expandablelistitem.xml
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/menu/menu_expandablelistitem.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/menu/menu_expandablelistitem.xml
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/menu/menu_item_animation.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/menu/menu_item_animation.xml
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/menu/menu_item_animation.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/menu/menu_item_animation.xml
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/menu/menu_swipe_to_dismiss.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/menu/menu_swipe_to_dismiss.xml
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/menu/menu_swipe_to_dismiss.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/menu/menu_swipe_to_dismiss.xml
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/menu/swipe_to_dismiss.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/menu/swipe_to_dismiss.xml
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/menu/swipe_to_dismiss.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/menu/swipe_to_dismiss.xml
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/values/colors.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/values/strings.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/values/strings.xml
similarity index 100%
rename from listview-animations-demo/ListviewAnimationDemo/res/values/strings.xml
rename to listview-animations-demo/ListviewAnimationDemo/app/src/main/res/values/strings.xml
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/values/styles.xml b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..6ce89c7
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/listview-animations-demo/ListviewAnimationDemo/app/src/test/java/com/example/listviewanimationdemo/ExampleUnitTest.java b/listview-animations-demo/ListviewAnimationDemo/app/src/test/java/com/example/listviewanimationdemo/ExampleUnitTest.java
new file mode 100644
index 0000000..2669dea
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/app/src/test/java/com/example/listviewanimationdemo/ExampleUnitTest.java
@@ -0,0 +1,15 @@
+package com.example.listviewanimationdemo;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationDemo/build.gradle b/listview-animations-demo/ListviewAnimationDemo/build.gradle
new file mode 100644
index 0000000..be515a8
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/build.gradle
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/listview-animations-demo/ListviewAnimationDemo/gradle.properties b/listview-animations-demo/ListviewAnimationDemo/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationDemo/gradle/wrapper/gradle-wrapper.jar b/listview-animations-demo/ListviewAnimationDemo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/listview-animations-demo/ListviewAnimationDemo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/listview-animations-demo/ListviewAnimationDemo/gradle/wrapper/gradle-wrapper.properties b/listview-animations-demo/ListviewAnimationDemo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7f33594
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Nov 05 12:26:32 CST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
diff --git a/listview-animations-demo/ListviewAnimationDemo/gradlew b/listview-animations-demo/ListviewAnimationDemo/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/listview-animations-demo/ListviewAnimationDemo/gradlew.bat b/listview-animations-demo/ListviewAnimationDemo/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/listview-animations-demo/ListviewAnimationDemo/res/layout/item_list_row.xml b/listview-animations-demo/ListviewAnimationDemo/res/layout/item_list_row.xml
deleted file mode 100644
index a22709c..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/res/layout/item_list_row.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
diff --git a/listview-animations-demo/ListviewAnimationDemo/settings.gradle b/listview-animations-demo/ListviewAnimationDemo/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/listview-animations-demo/ListviewAnimationDemo/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/AnimateDismissActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/AnimateDismissActivity.java
deleted file mode 100644
index 76d662f..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/AnimateDismissActivity.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.example.listviewanimationdemo;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.Button;
-import android.widget.CheckedTextView;
-import android.widget.ListView;
-
-import com.example.listviewanimationdemo.base.BaseListActivity;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.AnimateDismissAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.OnDismissCallback;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class AnimateDismissActivity extends FragmentActivity {
-
- private List mSelectedPositions;
- private MyListAdapter mAdapter;
-
-
- public static void actionToAnimateDismiss(Context context) {
- Intent intent = new Intent(context, AnimateDismissActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_animateremoval);
-
- mSelectedPositions = new ArrayList();
-
- ListView listView = (ListView) findViewById(R.id.activity_animateremoval_listview);
- mAdapter = new MyListAdapter(BaseListActivity.getItems());
- final AnimateDismissAdapter animateDismissAdapter = new AnimateDismissAdapter(mAdapter, new MyOnDismissCallback());
- animateDismissAdapter.setAbsListView(listView);
- listView.setAdapter(animateDismissAdapter);
-
- Button button = (Button) findViewById(R.id.activity_animateremoval_button);
- button.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(final View v) {
- animateDismissAdapter.animateDismiss(mSelectedPositions);
- mSelectedPositions.clear();
- }
- });
-
- listView.setOnItemClickListener(new OnItemClickListener() {
-
- @Override
- public void onItemClick(final AdapterView> parent, final View view, final int position, final long id) {
- CheckedTextView tv = (CheckedTextView) view;
- tv.toggle();
- if (tv.isChecked()) {
- mSelectedPositions.add(position);
- } else {
- mSelectedPositions.remove((Integer) position);
- }
- }
- });
- }
-
- private class MyOnDismissCallback implements OnDismissCallback {
-
- @Override
- public void onDismiss(final AbsListView listView, final int[] reverseSortedPositions) {
- for (int position : reverseSortedPositions) {
- mAdapter.remove(position);
- }
- }
- }
-
- private class MyListAdapter extends ArrayAdapter {
-
- public MyListAdapter(final ArrayList items) {
- super(items);
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- CheckedTextView tv = (CheckedTextView) convertView;
- if (tv == null) {
- tv = (CheckedTextView) LayoutInflater.from(AnimateDismissActivity.this).inflate(R.layout.item_animateremoval_row, parent, false);
- }
- tv.setText(String.valueOf(getItem(position)));
- tv.setChecked(mSelectedPositions.contains(position));
- return tv;
- }
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/DragAndDropActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/DragAndDropActivity.java
deleted file mode 100644
index 12799ca..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/DragAndDropActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.example.listviewanimationdemo;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.view.Gravity;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.example.listviewanimationdemo.base.BaseListActivity;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.AlphaInAnimationAdapter;
-import com.nhaarman.listviewanimations.widget.DynamicListView;
-
-public class DragAndDropActivity extends BaseListActivity {
-
- public static void actionToDragAndDrop(Context context) {
- Intent intent = new Intent(context, DragAndDropActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_draganddrop);
-
- DynamicListView listView = (DynamicListView) findViewById(R.id.activity_draganddrop_listview);
- listView.setDivider(null);
-
- TextView headerView =new TextView(this);
- headerView.setText("这是头部");
- headerView.setTextColor(Color.RED);
- headerView.setGravity(Gravity.CENTER);
- listView.addHeaderView(headerView);
-
- final ArrayAdapter adapter = createListAdapter();
- AlphaInAnimationAdapter animAdapter = new AlphaInAnimationAdapter(adapter);
- animAdapter.setInitialDelayMillis(100);
- animAdapter.setAbsListView(listView);
- listView.setAdapter(animAdapter);
-
- Toast.makeText(this, "长按item开始拖拽", Toast.LENGTH_LONG).show();
- listView.setOnItemMovedListener(new DynamicListView.OnItemMovedListener() {
- @Override
- public void onItemMoved(final int newPosition) {
- Toast.makeText(getApplicationContext(), adapter.getItem(newPosition) + " 移动到位置" + newPosition, Toast.LENGTH_SHORT).show();
- }
- });
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/ExpandableListItemActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/ExpandableListItemActivity.java
deleted file mode 100644
index e1ea154..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/ExpandableListItemActivity.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.example.listviewanimationdemo;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Bundle;
-import android.support.v4.util.LruCache;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.example.listviewanimationdemo.base.BaseListActivity;
-import com.nhaarman.listviewanimations.itemmanipulation.ExpandableListItemAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.AlphaInAnimationAdapter;
-
-import java.util.List;
-
-public class ExpandableListItemActivity extends BaseListActivity {
-
- private MyExpandableListItemAdapter mExpandableListItemAdapter;
- private boolean mLimited;
-
- public static void actionToExpandableList(Context context) {
- Intent intent = new Intent(context, ExpandableListItemActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mExpandableListItemAdapter = new MyExpandableListItemAdapter(this, getItems());
- AlphaInAnimationAdapter alphaInAnimationAdapter = new AlphaInAnimationAdapter(mExpandableListItemAdapter);
- alphaInAnimationAdapter.setAbsListView(getListView());
- alphaInAnimationAdapter.setInitialDelayMillis(500);
- getListView().setAdapter(alphaInAnimationAdapter);
-
- Toast.makeText(this, "点击卡片进行展开收缩操作", Toast.LENGTH_LONG).show();
- }
-
- private static class MyExpandableListItemAdapter extends ExpandableListItemAdapter {
-
- private final Context mContext;
- private final LruCache mMemoryCache;
-
- /**
- * Creates a new ExpandableListItemAdapter with the specified list, or an empty list if
- * items == null.
- */
- private MyExpandableListItemAdapter(final Context context, final List items) {
- super(context, R.layout.item_expandablelistitem_card, R.id.activity_expandablelistitem_card_title, R.id.activity_expandablelistitem_card_content, items);
- mContext = context;
-
- final int cacheSize = (int) (Runtime.getRuntime().maxMemory() / 1024);
- mMemoryCache = new LruCache(cacheSize) {
- @Override
- protected int sizeOf(final Integer key, final Bitmap bitmap) {
- // The cache size will be measured in kilobytes rather than
- // number of items.
- return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
- }
- };
- }
-
- @Override
- public View getTitleView(final int position, final View convertView, final ViewGroup parent) {
- TextView tv = (TextView) convertView;
- if (tv == null) {
- tv = new TextView(mContext);
- }
- tv.setText("卡片"+position);
- return tv;
- }
-
- @Override
- public View getContentView(final int position, final View convertView, final ViewGroup parent) {
- ImageView imageView = (ImageView) convertView;
- if (imageView == null) {
- imageView = new ImageView(mContext);
- imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
- }
-
- int imageResId;
- switch (getItem(position) % 5) {
- case 0:
- imageResId = R.drawable.img_nature1;
- break;
- case 1:
- imageResId = R.drawable.img_nature2;
- break;
- case 2:
- imageResId = R.drawable.img_nature3;
- break;
- case 3:
- imageResId = R.drawable.img_nature4;
- break;
- default:
- imageResId = R.drawable.img_nature5;
- }
-
- Bitmap bitmap = getBitmapFromMemCache(imageResId);
- if (bitmap == null) {
- bitmap = BitmapFactory.decodeResource(mContext.getResources(), imageResId);
- addBitmapToMemoryCache(imageResId, bitmap);
- }
- imageView.setImageBitmap(bitmap);
-
- return imageView;
- }
-
- private void addBitmapToMemoryCache(final int key, final Bitmap bitmap) {
- if (getBitmapFromMemCache(key) == null) {
- mMemoryCache.put(key, bitmap);
- }
- }
-
- private Bitmap getBitmapFromMemCache(final int key) {
- return mMemoryCache.get(key);
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- getMenuInflater().inflate(R.menu.menu_expandablelistitem, menu);
- return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_expandable_limit:
- mLimited = !mLimited;
- item.setChecked(mLimited);
- mExpandableListItemAdapter.setLimit(mLimited ? 2 : 0);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/HomeActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/HomeActivity.java
deleted file mode 100644
index d34cf1c..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/HomeActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.example.listviewanimationdemo;
-
-import com.example.listviewanimationdemo.itemanimation.ItemAnimationMainActivity;
-import com.example.listviewanimationdemo.itemmanipulation.AnimateAdditionActivity;
-import com.example.listviewanimationdemo.itemmanipulation.AnimateDismissActivity;
-import com.example.listviewanimationdemo.itemmanipulation.DragAndDropActivity;
-import com.example.listviewanimationdemo.itemmanipulation.ExpandableListItemActivity;
-import com.example.listviewanimationdemo.itemmanipulation.SwipeToDismissActivity;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Toast;
-
-public class HomeActivity extends FragmentActivity implements OnClickListener {
- @Override
- public void onCreate(Bundle bundle) {
- super.onCreate(bundle);
- setTitle("ListAnimation动画示例");
- setContentView(R.layout.activity_home);
- initView();
- }
-
- void initView() {
- findViewById(R.id.home_item_animation_btn).setOnClickListener(this);
- findViewById(R.id.home_swipe_to_dismiss_btn).setOnClickListener(this);
- findViewById(R.id.home_item_drag_btn).setOnClickListener(this);
- findViewById(R.id.home_multiple_item_dismiss_btn).setOnClickListener(
- this);
- findViewById(R.id.home_item_expend_btn).setOnClickListener(this);
- findViewById(R.id.home_item_add_btn).setOnClickListener(this);
- findViewById(R.id.home_item_go_github).setOnClickListener(this);
- findViewById(R.id.home_item_go_la_github).setOnClickListener(this);
- findViewById(R.id.home_item_go_demo_author_blog).setOnClickListener(this);
- }
-
- @Override
- public void onClick(View v) {
- int id = v.getId();
- switch (id) {
- case R.id.home_item_animation_btn:
- ItemAnimationMainActivity.actionToItemAnimationMain(this);
- break;
- case R.id.home_swipe_to_dismiss_btn:
- SwipeToDismissActivity.actionToSwipeDismiss(this);
- break;
- case R.id.home_item_drag_btn:
- DragAndDropActivity.actionToDragAndDrop(this);
- break;
- case R.id.home_multiple_item_dismiss_btn:
- AnimateDismissActivity.actionToAnimateDismiss(this);
- break;
- case R.id.home_item_expend_btn:
- ExpandableListItemActivity.actionToExpandableList(this);
- break;
- case R.id.home_item_add_btn:
- AnimateAdditionActivity.actionToItemAddition(this);
- break;
-
- case R.id.home_item_go_github:
- Toast.makeText(this, "正在转向demo项目主页...", Toast.LENGTH_SHORT).show();
- openBrower("https://github.com/android-cn/android-open-project-demo");
- break;
- case R.id.home_item_go_la_github:
- Toast.makeText(this, "正在转向ListviewAnimation项目主页...",
- Toast.LENGTH_SHORT).show();
- openBrower("https://github.com/nhaarman/ListViewAnimations");
- break;
- case R.id.home_item_go_demo_author_blog:
- Toast.makeText(this, "正在转向Demo作者博客主页...",
- Toast.LENGTH_SHORT).show();
- openBrower("http://waylife.github.io");
- break;
- default:
- break;
- }
- }
-
- public void openBrower(String url) {
- Uri uri = Uri.parse(url);
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.setPackage("com.android.browser");
- startActivity(intent);
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/SwipeToDismissActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/SwipeToDismissActivity.java
deleted file mode 100644
index 997add4..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/SwipeToDismissActivity.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package com.example.listviewanimationdemo;
-
-import java.util.Arrays;
-
-import com.example.listviewanimationdemo.base.BaseListActivity;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.OnDismissCallback;
-import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.SwipeDismissAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.contextualundo.ContextualUndoAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.contextualundo.ContextualUndoAdapter.CountDownFormatter;
-import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.contextualundo.ContextualUndoAdapter.DeleteItemCallback;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class SwipeToDismissActivity extends BaseListActivity implements OnDismissCallback, DeleteItemCallback {
-
- public static void actionToSwipeDismiss(Context context) {
- Intent intent = new Intent(context, SwipeToDismissActivity.class);
- context.startActivity(intent);
- }
-
- private ArrayAdapter mAdapter;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mAdapter = createListAdapter();
- setSwipeDismissAdapter();
- }
-
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.swipe_to_dismiss, menu);
- return true;
- }
-
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
- int id = item.getItemId();
- switch (id) {
- case R.id.swipe_menu_main:
- setSwipeDismissAdapter();
- break;
- case R.id.swipe_menu_with_undo:
- setContextualUndoAdapter();
- break;
- case R.id.swipe_menu_with_undo_2:
- setContextualUndoWithTimedDeleteAdapter();
- break;
- case R.id.swipe_menu_with_undo_3:
- setContextualUndoWithTimedDeleteAndCountDownAdapter();
- break;
- default:
- break;
- }
- return true;
- }
-
-
- void initView() {
- // findViewById(R.id.home_item_animation_btn).setOnClickListener(this);
- // findViewById(R.id.home_item_expend_btn).setOnClickListener(this);
- // findViewById(R.id.home_swipe_to_dismiss_btn).setOnClickListener(this);
- }
-
-
- private void setSwipeDismissAdapter() {
- SwipeDismissAdapter adapter = new SwipeDismissAdapter(mAdapter, this);
- adapter.setAbsListView(getListView());
- getListView().setAdapter(adapter);
- }
-
- @Override
- public void onDismiss(final AbsListView listView, final int[] reverseSortedPositions) {
- for (int position : reverseSortedPositions) {
- mAdapter.remove(position);
- }
- Toast.makeText(this, "Removed positions: " + Arrays.toString(reverseSortedPositions), Toast.LENGTH_SHORT).show();
- }
-
- private void setContextualUndoAdapter() {
- ContextualUndoAdapter adapter = new ContextualUndoAdapter(mAdapter, R.layout.item_undo_row, R.id.undo_row_undobutton, this);
- adapter.setAbsListView(getListView());
- getListView().setAdapter(adapter);
- }
-
- @Override
- public void deleteItem(final int position) {
- mAdapter.remove(position);
- mAdapter.notifyDataSetChanged();
- }
-
- private void setContextualUndoWithTimedDeleteAdapter() {
- ContextualUndoAdapter adapter = new ContextualUndoAdapter(mAdapter, R.layout.item_undo_row, R.id.undo_row_undobutton, 3000, this);
- adapter.setAbsListView(getListView());
- getListView().setAdapter(adapter);
- }
-
- private void setContextualUndoWithTimedDeleteAndCountDownAdapter() {
- ContextualUndoAdapter adapter = new ContextualUndoAdapter(mAdapter, R.layout.item_undo_row, R.id.undo_row_undobutton, 3000, R.id.undo_row_texttv, this, new MyFormatCountDownCallback());
- adapter.setAbsListView(getListView());
- getListView().setAdapter(adapter);
- }
-
- private class MyFormatCountDownCallback implements CountDownFormatter {
-
- @Override
- public String getCountDownString(final long millisUntilFinished) {
- int seconds = (int) Math.ceil(millisUntilFinished / 1000.0);
-
- if (seconds > 0) {
- return seconds+"s";
- }
- return "...";
- }
- }
-
-
- private class AnimSelectionAdapter extends ArrayAdapter {
-
- public AnimSelectionAdapter() {
- addAll("Swipe-To-Dismiss", "Contextual Undo", "CU - Timed Delete", "CU - Count Down");
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- TextView tv = (TextView) convertView;
- if (tv == null) {
- tv = (TextView) LayoutInflater.from(SwipeToDismissActivity.this).inflate(android.R.layout.simple_list_item_1, parent, false);
- }
-
- tv.setText(getItem(position));
-
- return tv;
- }
- }
-
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/base/BaseListActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/base/BaseListActivity.java
deleted file mode 100644
index 58d8ddb..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/base/BaseListActivity.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.example.listviewanimationdemo.base;
-
-import java.util.ArrayList;
-
-
-import com.example.listviewanimationdemo.R;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-import android.widget.TextView;
-
-public class BaseListActivity extends FragmentActivity {
-
- private ListView mListView;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_baselist);
- mListView = (ListView) findViewById(R.id.activity_baselist_listview);
- mListView.setDivider(null);
- }
-
- public ListView getListView() {
- return mListView;
- }
-
- protected ArrayAdapter createListAdapter() {
- return new MyListAdapter(this, getItems());
- }
-
- public static ArrayList getItems() {
- ArrayList items = new ArrayList();
- for (int i = 0; i < 1000; i++) {
- items.add(i);
- }
- return items;
- }
-
- private static class MyListAdapter extends ArrayAdapter {
-
- private final Context mContext;
-
- public MyListAdapter(final Context context,
- final ArrayList items) {
- super(items);
- mContext = context;
- }
-
- @Override
- public long getItemId(final int position) {
- return getItem(position).hashCode();
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- @Override
- public View getView(final int position, final View convertView,
- final ViewGroup parent) {
- TextView tv = (TextView) convertView;
- if (tv == null) {
- tv = (TextView) LayoutInflater.from(mContext).inflate(
- R.layout.item_list_row, parent, false);
- }
- tv.setText("第 " + getItem(position)+"行");
- return tv;
- }
- }
-
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/GoogleCardsActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/GoogleCardsActivity.java
deleted file mode 100644
index de30cdc..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/GoogleCardsActivity.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.example.listviewanimationdemo.itemanimation;
-
-import java.util.ArrayList;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.util.LruCache;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.example.listviewanimationdemo.R;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.OnDismissCallback;
-import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.SwipeDismissAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.SwingBottomInAnimationAdapter;
-
-public class GoogleCardsActivity extends FragmentActivity implements OnDismissCallback {
-
- private GoogleCardsAdapter mGoogleCardsAdapter;
-
- public static void actionToGoogleCard(Context context){
- Intent intent =new Intent(context,GoogleCardsActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setTitle("Google Card");
- setContentView(R.layout.activity_googlecards);
-
- ListView listView = (ListView) findViewById(R.id.activity_googlecards_listview);
-
- mGoogleCardsAdapter = new GoogleCardsAdapter(this);
- SwingBottomInAnimationAdapter swingBottomInAnimationAdapter = new SwingBottomInAnimationAdapter(new SwipeDismissAdapter(mGoogleCardsAdapter, this));
- swingBottomInAnimationAdapter.setInitialDelayMillis(300);
- swingBottomInAnimationAdapter.setAbsListView(listView);
-
- listView.setAdapter(swingBottomInAnimationAdapter);
-
- mGoogleCardsAdapter.addAll(getItems());
- }
-
- private ArrayList getItems() {
- ArrayList items = new ArrayList();
- for (int i = 0; i < 100; i++) {
- items.add(i);
- }
- return items;
- }
-
- @Override
- public void onDismiss(final AbsListView listView, final int[] reverseSortedPositions) {
- for (int position : reverseSortedPositions) {
- mGoogleCardsAdapter.remove(position);
- }
- }
-
- private static class GoogleCardsAdapter extends ArrayAdapter {
-
- private final Context mContext;
- private final LruCache mMemoryCache;
-
- public GoogleCardsAdapter(final Context context) {
- mContext = context;
-
- final int cacheSize = (int) (Runtime.getRuntime().maxMemory() / 1024);
- mMemoryCache = new LruCache(cacheSize) {
- @Override
- protected int sizeOf(final Integer key, final Bitmap bitmap) {
- // The cache size will be measured in kilobytes rather than
- // number of items.
- return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
- }
- };
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- ViewHolder viewHolder;
- View view = convertView;
- if (view == null) {
- view = LayoutInflater.from(mContext).inflate(R.layout.activity_googlecards_card, parent, false);
-
- viewHolder = new ViewHolder();
- viewHolder.textView = (TextView) view.findViewById(R.id.activity_googlecards_card_textview);
- view.setTag(viewHolder);
-
- viewHolder.imageView = (ImageView) view.findViewById(R.id.activity_googlecards_card_imageview);
- } else {
- viewHolder = (ViewHolder) view.getTag();
- }
-
- viewHolder.textView.setText("这是第 " + (getItem(position) + 1)+"个卡片");
- setImageView(viewHolder, position);
-
- return view;
- }
-
- private void setImageView(final ViewHolder viewHolder, final int position) {
- int imageResId;
- switch (getItem(position) % 5) {
- case 0:
- imageResId = R.drawable.img_nature1;
- break;
- case 1:
- imageResId = R.drawable.img_nature2;
- break;
- case 2:
- imageResId = R.drawable.img_nature3;
- break;
- case 3:
- imageResId = R.drawable.img_nature4;
- break;
- default:
- imageResId = R.drawable.img_nature5;
- }
-
- Bitmap bitmap = getBitmapFromMemCache(imageResId);
- if (bitmap == null) {
- bitmap = BitmapFactory.decodeResource(mContext.getResources(), imageResId);
- addBitmapToMemoryCache(imageResId, bitmap);
- }
- viewHolder.imageView.setImageBitmap(bitmap);
- }
-
- private void addBitmapToMemoryCache(final int key, final Bitmap bitmap) {
- if (getBitmapFromMemCache(key) == null) {
- mMemoryCache.put(key, bitmap);
- }
- }
-
- private Bitmap getBitmapFromMemCache(final int key) {
- return mMemoryCache.get(key);
- }
-
- private static class ViewHolder {
- TextView textView;
- ImageView imageView;
- }
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/GridViewActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/GridViewActivity.java
deleted file mode 100644
index f6371fe..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/GridViewActivity.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package com.example.listviewanimationdemo.itemanimation;
-
-import java.util.ArrayList;
-import java.util.List;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.util.LruCache;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.GridView;
-import android.widget.ImageView;
-
-import com.example.listviewanimationdemo.R;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.SwingBottomInAnimationAdapter;
-
-public class GridViewActivity extends FragmentActivity {
-
- public static void actionToGridView(Context context) {
- Intent intent = new Intent(context, GridViewActivity.class);
- context.startActivity(intent);
- }
-
- @SuppressLint("InlinedApi")
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- getWindow()
- .addFlags(
- android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
- }
- super.onCreate(savedInstanceState);
- setTitle("Gridview动画");
- setContentView(R.layout.activity_gridview);
-
- GridView gridView = (GridView) findViewById(R.id.activity_gridview_gv);
- SwingBottomInAnimationAdapter swingBottomInAnimationAdapter = new SwingBottomInAnimationAdapter(
- new MyAdapter(this, getItems()));
- swingBottomInAnimationAdapter.setAbsListView(gridView);
- swingBottomInAnimationAdapter.setInitialDelayMillis(300);
- gridView.setAdapter(swingBottomInAnimationAdapter);
-
- }
-
- private ArrayList getItems() {
- ArrayList items = new ArrayList();
- for (int i = 0; i < 100; i++) {
- items.add(i);
- }
- return items;
- }
-
- private static class MyAdapter extends ArrayAdapter {
-
- private final Context mContext;
- private final LruCache mMemoryCache;
-
- public MyAdapter(final Context context, final List list) {
- super(list);
- mContext = context;
-
- final int cacheSize = (int) (Runtime.getRuntime().maxMemory() / 1024);
- mMemoryCache = new LruCache(cacheSize) {
- @Override
- protected int sizeOf(final Integer key, final Bitmap bitmap) {
- // The cache size will be measured in kilobytes rather than
- // number of items.
- return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
- }
- };
- }
-
- @Override
- public View getView(final int position, final View convertView,
- final ViewGroup viewGroup) {
- ImageView imageView = (ImageView) convertView;
-
- if (imageView == null) {
- imageView = new ImageView(mContext);
- imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
- }
-
- int imageResId;
- switch (getItem(position) % 5) {
- case 0:
- imageResId = R.drawable.img_nature1;
- break;
- case 1:
- imageResId = R.drawable.img_nature2;
- break;
- case 2:
- imageResId = R.drawable.img_nature3;
- break;
- case 3:
- imageResId = R.drawable.img_nature4;
- break;
- default:
- imageResId = R.drawable.img_nature5;
- }
-
- Bitmap bitmap = getBitmapFromMemCache(imageResId);
- if (bitmap == null) {
- bitmap = BitmapFactory.decodeResource(mContext.getResources(),
- imageResId);
- addBitmapToMemoryCache(imageResId, bitmap);
- }
- imageView.setImageBitmap(bitmap);
-
- return imageView;
- }
-
- private void addBitmapToMemoryCache(final int key, final Bitmap bitmap) {
- if (getBitmapFromMemCache(key) == null) {
- mMemoryCache.put(key, bitmap);
- }
- }
-
- private Bitmap getBitmapFromMemCache(final int key) {
- return mMemoryCache.get(key);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/ItemAnimationActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/ItemAnimationActivity.java
deleted file mode 100644
index 1446320..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/ItemAnimationActivity.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.example.listviewanimationdemo.itemanimation;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-
-import com.example.listviewanimationdemo.R;
-import com.example.listviewanimationdemo.base.BaseListActivity;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.AnimationAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.AlphaInAnimationAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.ScaleInAnimationAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.SwingBottomInAnimationAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.SwingLeftInAnimationAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.SwingRightInAnimationAdapter;
-
-import java.util.ArrayList;
-
-public class ItemAnimationActivity extends BaseListActivity {
-
- private BaseAdapter mAdapter;
-
- public static void actionToItemAnimation(Context context) {
- Intent intent = new Intent(context, ItemAnimationActivity.class);
- context.startActivity(intent);
- }
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setTitle("item动画");
- mAdapter = new MyAdapter(this, getItems());
- setAlphaAdapter();
- }
-
- private void setAlphaAdapter() {
- AnimationAdapter animAdapter = new AlphaInAnimationAdapter(mAdapter);
- animAdapter.setAbsListView(getListView());
- getListView().setAdapter(animAdapter);
- }
-
- private void setLeftAdapter() {
- AnimationAdapter animAdapter = new SwingLeftInAnimationAdapter(mAdapter);
- animAdapter.setAbsListView(getListView());
- getListView().setAdapter(animAdapter);
- }
-
- private void setRightAdapter() {
- AnimationAdapter animAdapter = new SwingRightInAnimationAdapter(
- mAdapter);
- animAdapter.setAbsListView(getListView());
- getListView().setAdapter(animAdapter);
- }
-
- private void setBottomAdapter() {
- AnimationAdapter animAdapter = new SwingBottomInAnimationAdapter(
- mAdapter);
- animAdapter.setAbsListView(getListView());
- getListView().setAdapter(animAdapter);
- }
-
- private void setBottomRightAdapter() {
- AnimationAdapter animAdapter = new SwingBottomInAnimationAdapter(
- new SwingRightInAnimationAdapter(mAdapter));
- animAdapter.setAbsListView(getListView());
- getListView().setAdapter(animAdapter);
- }
-
- private void setScaleAdapter() {
- AnimationAdapter animAdapter = new ScaleInAnimationAdapter(mAdapter);
- animAdapter.setAbsListView(getListView());
- getListView().setAdapter(animAdapter);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.menu_item_animation, menu);
- return true;
- }
-
- @Override
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
- int id = item.getItemId();
- switch (id) {
- case R.id.menu_item_animation_alpha:
- setAlphaAdapter();
- break;
- case R.id.menu_item_animation_left:
- setLeftAdapter();
- break;
- case R.id.menu_item_animation_right:
- setRightAdapter();
- break;
- case R.id.menu_item_animation_bottom:
- setBottomAdapter();
- break;
- case R.id.menu_item_animation_bottom_right:
- setBottomRightAdapter();
- break;
- case R.id.menu_item_animation_scale:
- setScaleAdapter();
- break;
- default:
- break;
- }
- return true;
- }
-
- /* Non-ListViewAnimations related stuff below */
-
- class MyAdapter extends ArrayAdapter {
-
- private final Context mContext;
-
- public MyAdapter(final Context context, final ArrayList items) {
- super(items);
- mContext = context;
- }
-
- @Override
- public long getItemId(final int position) {
- return getItem(position).hashCode();
- }
-
- @Override
- public View getView(final int position, final View convertView,
- final ViewGroup parent) {
- TextView tv = (TextView) convertView;
- if (tv == null) {
- tv = (TextView) LayoutInflater.from(mContext).inflate(
- R.layout.item_list_row, parent, false);
- }
- tv.setText("******第 " + getItem(position) + "行******");
- return tv;
- }
- }
-}
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/ItemAnimationMainActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/ItemAnimationMainActivity.java
deleted file mode 100644
index 2f66a6b..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemanimation/ItemAnimationMainActivity.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.example.listviewanimationdemo.itemanimation;
-
-import com.example.listviewanimationdemo.R;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.view.View;
-import android.view.View.OnClickListener;
-
-public class ItemAnimationMainActivity extends FragmentActivity implements
- OnClickListener {
-
- public static void actionToItemAnimationMain(Context context) {
- Intent intent = new Intent(context, ItemAnimationMainActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- public void onCreate(Bundle bundle) {
- super.onCreate(bundle);
- setTitle("item动画主页");
- setContentView(R.layout.activity_item_animation_main);
- findViewById(R.id.item_animation_main_1).setOnClickListener(this);
- findViewById(R.id.item_animation_main_google_card).setOnClickListener(
- this);
- findViewById(R.id.item_animation_main_gridview)
- .setOnClickListener(this);
- }
-
- @Override
- public void onClick(View v) {
- int id = v.getId();
- switch (id) {
- case R.id.item_animation_main_1:
- ItemAnimationActivity.actionToItemAnimation(this);
- break;
- case R.id.item_animation_main_google_card:
- GoogleCardsActivity.actionToGoogleCard(this);
- break;
- case R.id.item_animation_main_gridview:
- GridViewActivity.actionToGridView(this);
- break;
- default:
- break;
- }
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/AnimateAdditionActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/AnimateAdditionActivity.java
deleted file mode 100644
index 8e72a86..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/AnimateAdditionActivity.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.example.listviewanimationdemo.itemmanipulation;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.example.listviewanimationdemo.R;
-import com.example.listviewanimationdemo.base.BaseListActivity;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.AnimateAdditionAdapter;
-
-import java.util.ArrayList;
-
-public class AnimateAdditionActivity extends BaseListActivity implements
- AdapterView.OnItemClickListener {
-
- private int mAddedItemNumber;
- private AnimateAdditionAdapter mAnimateAdditionAdapter;
-
- public static void actionToItemAddition(Context context) {
- Intent intent = new Intent(context, AnimateAdditionActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setTitle("增加item");
- MyAdapter myAdapter = new MyAdapter(this, getStringItems());
-
- mAnimateAdditionAdapter = new AnimateAdditionAdapter(myAdapter);
- mAnimateAdditionAdapter.setListView(getListView());
-
- getListView().setAdapter(mAnimateAdditionAdapter);
- getListView().setOnItemClickListener(this);
-
- Toast.makeText(this, "点击某一项来增加一项", Toast.LENGTH_LONG).show();
- }
-
- private static ArrayList getStringItems() {
- ArrayList items = new ArrayList();
- for (int i = 0; i < 1000; i++) {
- items.add(">>>第 " + i + "行===");
- }
- return items;
- }
-
- @Override
- public void onItemClick(final AdapterView> parent, final View view,
- final int position, final long id) {
- mAnimateAdditionAdapter.insert(position, "***新增加的item: "
- + mAddedItemNumber);
- mAddedItemNumber++;
- }
-
- private static class MyAdapter extends ArrayAdapter {
-
- private final Context mContext;
-
- public MyAdapter(final Context context, final ArrayList items) {
- super(items);
- mContext = context;
- }
-
- @Override
- public long getItemId(final int position) {
- return getItem(position).hashCode();
- }
-
- @Override
- public View getView(final int position, final View convertView,
- final ViewGroup parent) {
- TextView tv = (TextView) convertView;
- if (tv == null) {
- tv = (TextView) LayoutInflater.from(mContext).inflate(
- R.layout.item_list_row, parent, false);
- }
- tv.setText(getItem(position));
- return tv;
- }
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/DragAndDropActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/DragAndDropActivity.java
deleted file mode 100644
index 99d033e..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/DragAndDropActivity.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.example.listviewanimationdemo.itemmanipulation;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.view.Gravity;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.example.listviewanimationdemo.R;
-import com.example.listviewanimationdemo.R.id;
-import com.example.listviewanimationdemo.R.layout;
-import com.example.listviewanimationdemo.base.BaseListActivity;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.swinginadapters.prepared.AlphaInAnimationAdapter;
-import com.nhaarman.listviewanimations.widget.DynamicListView;
-
-public class DragAndDropActivity extends BaseListActivity {
-
- public static void actionToDragAndDrop(Context context) {
- Intent intent = new Intent(context, DragAndDropActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setTitle("item拖动");
- setContentView(R.layout.activity_draganddrop);
-
- DynamicListView listView = (DynamicListView) findViewById(R.id.activity_draganddrop_listview);
- listView.setDivider(null);
-
- TextView headerView =new TextView(this);
- headerView.setText("这是头部");
- headerView.setTextColor(Color.RED);
- headerView.setGravity(Gravity.CENTER);
- listView.addHeaderView(headerView);
-
- final ArrayAdapter adapter = createListAdapter();
- AlphaInAnimationAdapter animAdapter = new AlphaInAnimationAdapter(adapter);
- animAdapter.setInitialDelayMillis(100);
- animAdapter.setAbsListView(listView);
- listView.setAdapter(animAdapter);
-
- Toast.makeText(this, "长按item开始拖拽", Toast.LENGTH_LONG).show();
- listView.setOnItemMovedListener(new DynamicListView.OnItemMovedListener() {
- @Override
- public void onItemMoved(final int newPosition) {
- Toast.makeText(getApplicationContext(), adapter.getItem(newPosition) + " 移动到位置" + newPosition, Toast.LENGTH_SHORT).show();
- }
- });
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/SwipeToDismissActivity.java b/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/SwipeToDismissActivity.java
deleted file mode 100644
index fced4f7..0000000
--- a/listview-animations-demo/ListviewAnimationDemo/src/com/example/listviewanimationdemo/itemmanipulation/SwipeToDismissActivity.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package com.example.listviewanimationdemo.itemmanipulation;
-
-import java.util.Arrays;
-
-import com.example.listviewanimationdemo.R;
-import com.example.listviewanimationdemo.R.id;
-import com.example.listviewanimationdemo.R.layout;
-import com.example.listviewanimationdemo.R.menu;
-import com.example.listviewanimationdemo.base.BaseListActivity;
-import com.nhaarman.listviewanimations.ArrayAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.OnDismissCallback;
-import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.SwipeDismissAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.contextualundo.ContextualUndoAdapter;
-import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.contextualundo.ContextualUndoAdapter.CountDownFormatter;
-import com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.contextualundo.ContextualUndoAdapter.DeleteItemCallback;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class SwipeToDismissActivity extends BaseListActivity implements OnDismissCallback, DeleteItemCallback {
-
- public static void actionToSwipeDismiss(Context context) {
- Intent intent = new Intent(context, SwipeToDismissActivity.class);
- context.startActivity(intent);
- }
-
- private ArrayAdapter mAdapter;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setTitle("item滑动删除");
- mAdapter = createListAdapter();
- setSwipeDismissAdapter();
- }
-
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.menu_swipe_to_dismiss, menu);
- return true;
- }
-
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
- int id = item.getItemId();
- switch (id) {
- case R.id.swipe_menu_main:
- setSwipeDismissAdapter();
- break;
- case R.id.swipe_menu_with_undo:
- setContextualUndoAdapter();
- break;
- case R.id.swipe_menu_with_undo_2:
- setContextualUndoWithTimedDeleteAdapter();
- break;
- case R.id.swipe_menu_with_undo_3:
- setContextualUndoWithTimedDeleteAndCountDownAdapter();
- break;
- default:
- break;
- }
- return true;
- }
-
-
- void initView() {
- // findViewById(R.id.home_item_animation_btn).setOnClickListener(this);
- // findViewById(R.id.home_item_expend_btn).setOnClickListener(this);
- // findViewById(R.id.home_swipe_to_dismiss_btn).setOnClickListener(this);
- }
-
-
- private void setSwipeDismissAdapter() {
- SwipeDismissAdapter adapter = new SwipeDismissAdapter(mAdapter, this);
- adapter.setAbsListView(getListView());
- getListView().setAdapter(adapter);
- }
-
- @Override
- public void onDismiss(final AbsListView listView, final int[] reverseSortedPositions) {
- for (int position : reverseSortedPositions) {
- mAdapter.remove(position);
- }
- Toast.makeText(this, "Removed positions: " + Arrays.toString(reverseSortedPositions), Toast.LENGTH_SHORT).show();
- }
-
- private void setContextualUndoAdapter() {
- ContextualUndoAdapter adapter = new ContextualUndoAdapter(mAdapter, R.layout.item_undo_row, R.id.undo_row_undobutton, this);
- adapter.setAbsListView(getListView());
- getListView().setAdapter(adapter);
- }
-
- @Override
- public void deleteItem(final int position) {
- mAdapter.remove(position);
- mAdapter.notifyDataSetChanged();
- }
-
- private void setContextualUndoWithTimedDeleteAdapter() {
- ContextualUndoAdapter adapter = new ContextualUndoAdapter(mAdapter, R.layout.item_undo_row, R.id.undo_row_undobutton, 3000, this);
- adapter.setAbsListView(getListView());
- getListView().setAdapter(adapter);
- }
-
- private void setContextualUndoWithTimedDeleteAndCountDownAdapter() {
- ContextualUndoAdapter adapter = new ContextualUndoAdapter(mAdapter, R.layout.item_undo_row, R.id.undo_row_undobutton, 3000, R.id.undo_row_texttv, this, new MyFormatCountDownCallback());
- adapter.setAbsListView(getListView());
- getListView().setAdapter(adapter);
- }
-
- private class MyFormatCountDownCallback implements CountDownFormatter {
-
- @Override
- public String getCountDownString(final long millisUntilFinished) {
- int seconds = (int) Math.ceil(millisUntilFinished / 1000.0);
-
- if (seconds > 0) {
- return seconds+"s";
- }
- return "...";
- }
- }
-
-
- private class AnimSelectionAdapter extends ArrayAdapter {
-
- public AnimSelectionAdapter() {
- addAll("Swipe-To-Dismiss", "Contextual Undo", "CU - Timed Delete", "CU - Count Down");
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- TextView tv = (TextView) convertView;
- if (tv == null) {
- tv = (TextView) LayoutInflater.from(SwipeToDismissActivity.this).inflate(android.R.layout.simple_list_item_1, parent, false);
- }
-
- tv.setText(getItem(position));
-
- return tv;
- }
- }
-
-}
diff --git a/listview-animations-demo/ListviewAnimationLib/.classpath b/listview-animations-demo/ListviewAnimationLib/.classpath
deleted file mode 100644
index 5176974..0000000
--- a/listview-animations-demo/ListviewAnimationLib/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/listview-animations-demo/ListviewAnimationLib/.project b/listview-animations-demo/ListviewAnimationLib/.project
deleted file mode 100644
index b56e198..0000000
--- a/listview-animations-demo/ListviewAnimationLib/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
- Library-ListViewAnimations
-
-
-
-
-
- com.android.ide.eclipse.adt.ResourceManagerBuilder
-
-
-
-
- com.android.ide.eclipse.adt.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- com.android.ide.eclipse.adt.ApkBuilder
-
-
-
-
-
- com.android.ide.eclipse.adt.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
diff --git a/listview-animations-demo/ListviewAnimationLib/.settings/org.eclipse.jdt.core.prefs b/listview-animations-demo/ListviewAnimationLib/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index b080d2d..0000000
--- a/listview-animations-demo/ListviewAnimationLib/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/listview-animations-demo/ListviewAnimationLib/AndroidManifest.xml b/listview-animations-demo/ListviewAnimationLib/AndroidManifest.xml
deleted file mode 100644
index 3aa0362..0000000
--- a/listview-animations-demo/ListviewAnimationLib/AndroidManifest.xml
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationLib/build.gradle b/listview-animations-demo/ListviewAnimationLib/build.gradle
deleted file mode 100644
index 8134bdc..0000000
--- a/listview-animations-demo/ListviewAnimationLib/build.gradle
+++ /dev/null
@@ -1,44 +0,0 @@
-apply plugin: 'android-library'
-
-
-dependencies {
- compile 'com.nineoldandroids:library:2.4.0'
-
- instrumentTestCompile ('org.mockito:mockito-core:1.9.5') { exclude group: 'org.hamcrest' }
- instrumentTestCompile ('com.google.dexmaker:dexmaker-mockito:1.0') { exclude group: 'org.hamcrest' }
- instrumentTestCompile ('junit:junit:4.11') { exclude group: 'org.hamcrest' }
- instrumentTestCompile 'org.hamcrest:hamcrest-all:1.3'
-}
-
-android {
- compileSdkVersion 19
- buildToolsVersion '19.0.1'
-
- sourceSets {
- main {
- manifest.srcFile 'AndroidManifest.xml'
- java.srcDirs = ['src']
- resources.srcDirs = ['src']
- res.srcDirs = ['res']
- assets.srcDirs = ['assets']
- }
-
- instrumentTest.setRoot('tests')
- instrumentTest {
- java.srcDirs = ['tests/java']
- }
- }
-
- defaultConfig {
- minSdkVersion 8
- targetSdkVersion 19
- versionName project.VERSION_NAME
- versionCode Integer.parseInt(new Date().format('yyyyMMddHH'))
- }
-
- packagingOptions {
- exclude 'LICENSE.txt'
- }
-}
-
-apply from: '../maven_push.gradle'
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationLib/gradle.properties b/listview-animations-demo/ListviewAnimationLib/gradle.properties
deleted file mode 100644
index f7d3911..0000000
--- a/listview-animations-demo/ListviewAnimationLib/gradle.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-POM_NAME=ListViewAnimations Library
-POM_ARTIFACT_ID=library
-POM_PACKAGING=aar
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationLib/lint.xml b/listview-animations-demo/ListviewAnimationLib/lint.xml
deleted file mode 100644
index ee0eead..0000000
--- a/listview-animations-demo/ListviewAnimationLib/lint.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationLib/pom.xml b/listview-animations-demo/ListviewAnimationLib/pom.xml
deleted file mode 100644
index 8e8555e..0000000
--- a/listview-animations-demo/ListviewAnimationLib/pom.xml
+++ /dev/null
@@ -1,142 +0,0 @@
-
- 4.0.0
-
-
- com.haarman.listviewanimations
- parent
- 1.0.0-SNAPSHOT
-
-
- listviewanimations
- apklib
-
- ListViewAnimations (Library)
-
-
-
-
- com.google.android
- android
- provided
-
-
-
-
- com.nineoldandroids
- library
-
-
-
-
- junit
- junit
- test
-
-
-
-
- src
-
-
- com.jayway.maven.plugins.android.generation2
- android-maven-plugin
- true
-
-
- runLint
- compile
-
- lint
-
-
-
-
-
-
- org.codehaus.mojo
- build-helper-maven-plugin
-
-
- package
-
- attach-artifact
-
-
-
-
- jar
- ${project.build.directory}/${project.build.finalName}.jar
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- attach-sources
-
- jar
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
-
- attach-javadocs
-
- jar
-
-
- true
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-release-plugin
-
-
-
-
-
-
-
- release
-
-
- performRelease
- true
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- ${maven-gpg-plugin.version}
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/ArrayAdapter.java b/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/ArrayAdapter.java
deleted file mode 100644
index 84e16bb..0000000
--- a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/ArrayAdapter.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.nhaarman.listviewanimations;
-
-import android.widget.BaseAdapter;
-
-import com.nhaarman.listviewanimations.itemmanipulation.AnimateAdditionAdapter;
-import com.nhaarman.listviewanimations.widget.DynamicListView;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-
-/**
- * A {@code true} {@link ArrayList} adapter providing access to all ArrayList methods.
- * Also implements {@link DynamicListView.Swappable} for easy object swapping, and {@link AnimateAdditionAdapter.Insertable} for inserting objects.
- */
-@SuppressWarnings("UnusedDeclaration")
-public abstract class ArrayAdapter extends BaseAdapter implements List, DynamicListView.Swappable, AnimateAdditionAdapter.Insertable {
-
- protected List mItems;
-
- /**
- * Creates a new ArrayAdapter with an empty {@code List} .
- */
- public ArrayAdapter() {
- this(null);
- }
-
- /**
- * Creates a new {@link ArrayAdapter} using given {@code List} , or an empty {@code List} if objects == null.
- */
- public ArrayAdapter(final List objects) {
- this(objects, false);
- }
-
- /**
- * Creates a new {@link ArrayAdapter}, using (a copy of) given {@code List} , or an empty {@code List} if objects = null.
- * @param copyList {@code true} to create a copy of the {@code List} , {@code false} to reuse the reference.
- */
- public ArrayAdapter(final List objects, final boolean copyList) {
- if (objects != null) {
- if (copyList) {
- mItems = new ArrayList(objects);
- } else {
- mItems = objects;
- }
- } else {
- mItems = new ArrayList();
- }
- }
-
-
- @Override
- public int getCount() {
- return mItems.size();
- }
-
- @Override
- public T getItem(final int location) {
- return mItems.get(location);
- }
-
- @Override
- public long getItemId(final int location) {
- return location;
- }
-
- /**
- * Appends the specified element to the end of the {@code List} .
- * @param object the object to add.
- * @return always true.
- */
- @Override
- public boolean add(final T object) {
- boolean result = mItems.add(object);
- notifyDataSetChanged();
- return result;
- }
-
- @Override
- public void add(final int location, final T object) {
- mItems.add(location, object);
- notifyDataSetChanged();
- }
-
- /**
- * Adds the objects in the specified collection to the end of this List. The objects are added in the order in which they are returned from the collection's iterator.
- * @param collection the collection of objects.
- * @return {@code true} if this {@code List} is modified, {@code false} otherwise.
- */
- @Override
- public boolean addAll(final Collection extends T> collection) {
- boolean result = mItems.addAll(collection);
- notifyDataSetChanged();
- return result;
- }
-
- /**
- * Appends all of the elements in the specified collection to the end of the
- * {@code List} , in the order that they are specified.
- * @param objects the array of objects.
- * @return {@code true} if the collection changed during insertion.
- */
- public boolean addAll(final T... objects) {
- boolean result = Collections.addAll(mItems, objects);
- notifyDataSetChanged();
- return result;
- }
-
- @Override
- public boolean addAll(final int location, final Collection extends T> objects) {
- boolean result = mItems.addAll(location, objects);
- notifyDataSetChanged();
- return result;
- }
-
- /**
- * Inserts the objects in the specified collection at the specified location in this List. The objects are added in the order that they specified.
- * @param location the index at which to insert.
- * @param objects the array of objects.
- */
- public void addAll(final int location, final T... objects) {
- for (int i = location; i < objects.length + location; i++) {
- mItems.add(i, objects[i]);
- }
- notifyDataSetChanged();
- }
-
- @Override
- public void clear() {
- mItems.clear();
- notifyDataSetChanged();
- }
-
- @Override
- public boolean contains(final Object object) {
- return mItems.contains(object);
- }
-
- @Override
- public boolean containsAll(final Collection> collection) {
- return mItems.containsAll(collection);
- }
-
- @Override
- public T get(final int location) {
- return mItems.get(location);
- }
-
- @Override
- public T set(final int location, final T object) {
- T result = mItems.set(location, object);
- notifyDataSetChanged();
- return result;
- }
-
- @Override
- public int size() {
- return mItems.size();
- }
-
- @Override
- public List subList(final int start, final int end) {
- return mItems.subList(start, end);
- }
-
- @Override
- public Object[] toArray() {
- return mItems.toArray();
- }
-
- @Override
- public T1[] toArray(final T1[] array) {
- //noinspection SuspiciousToArrayCall
- return mItems.toArray(array);
- }
-
- @Override
- public boolean remove(final Object object) {
- boolean result = mItems.remove(object);
- notifyDataSetChanged();
- return result;
- }
-
- @Override
- public T remove(final int location) {
- T result = mItems.remove(location);
- notifyDataSetChanged();
- return result;
- }
-
- /**
- * Removes all elements at the specified locations in the {@code List} .
- * @param locations the collection of indexes to remove.
- * @return a collection containing the removed objects.
- */
- public Collection removePositions(final Collection locations) {
- ArrayList removedItems = new ArrayList();
-
- ArrayList locationsList = new ArrayList(locations);
- Collections.sort(locationsList);
- Collections.reverse(locationsList);
- for (int location : locationsList) {
- removedItems.add(mItems.remove(location));
- }
- notifyDataSetChanged();
- return removedItems;
- }
-
- @Override
- public boolean removeAll(final Collection> objects) {
- boolean result = mItems.removeAll(objects);
- notifyDataSetChanged();
- return result;
- }
-
- @Override
- public boolean retainAll(final Collection> objects) {
- boolean result = mItems.retainAll(objects);
- notifyDataSetChanged();
- return result;
- }
-
- @Override
- public int indexOf(final Object object) {
- return mItems.indexOf(object);
- }
-
- @Override
- public Iterator iterator() {
- return mItems.iterator();
- }
-
- @Override
- public int lastIndexOf(final Object object) {
- return mItems.lastIndexOf(object);
- }
-
- @Override
- public ListIterator listIterator() {
- return mItems.listIterator();
- }
-
- @Override
- public ListIterator listIterator(final int location) {
- return mItems.listIterator(location);
- }
-
- @Override
- public void swapItems(final int locationOne, final int locationTwo) {
- T temp = getItem(locationOne);
- set(locationOne, getItem(locationTwo));
- set(locationTwo, temp);
- }
-
- private BaseAdapter mDataSetChangedSlavedAdapter;
-
- public void propagateNotifyDataSetChanged(final BaseAdapter slavedAdapter) {
- mDataSetChangedSlavedAdapter = slavedAdapter;
- }
-
- @Override
- public void notifyDataSetChanged() {
- super.notifyDataSetChanged();
- if (mDataSetChangedSlavedAdapter != null) {
- mDataSetChangedSlavedAdapter.notifyDataSetChanged();
- }
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/BaseAdapterDecorator.java b/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/BaseAdapterDecorator.java
deleted file mode 100644
index 44aa2e1..0000000
--- a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/BaseAdapterDecorator.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.nhaarman.listviewanimations;
-
-import android.database.DataSetObserver;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseAdapter;
-import android.widget.SectionIndexer;
-
-import com.nhaarman.listviewanimations.widget.DynamicListView;
-import com.nhaarman.listviewanimations.widget.DynamicListView.Swappable;
-
-/**
- * A decorator class that enables decoration of an instance of the BaseAdapter
- * class.
- *
- * Classes extending this class can override methods and provide extra
- * functionality before or after calling the super method.
- */
-public abstract class BaseAdapterDecorator extends BaseAdapter implements SectionIndexer, DynamicListView.Swappable, ListViewSetter {
-
- protected final BaseAdapter mDecoratedBaseAdapter;
-
- private AbsListView mListView;
-
- private boolean mIsParentHorizontalScrollContainer;
- private int mResIdTouchChild;
-
- public BaseAdapterDecorator(final BaseAdapter baseAdapter) {
- mDecoratedBaseAdapter = baseAdapter;
- }
-
- @Override
- public void setAbsListView(final AbsListView listView) {
- mListView = listView;
-
- if (mDecoratedBaseAdapter instanceof ListViewSetter) {
- ((ListViewSetter) mDecoratedBaseAdapter).setAbsListView(listView);
- }
-
- if (mListView instanceof DynamicListView) {
- DynamicListView dynListView = (DynamicListView) mListView;
- dynListView.setIsParentHorizontalScrollContainer(mIsParentHorizontalScrollContainer);
- dynListView.setDynamicTouchChild(mResIdTouchChild);
- }
- }
-
- public AbsListView getAbsListView() {
- return mListView;
- }
-
- @Override
- public int getCount() {
- return mDecoratedBaseAdapter.getCount();
- }
-
- @Override
- public Object getItem(final int position) {
- return mDecoratedBaseAdapter.getItem(position);
- }
-
- @Override
- public long getItemId(final int position) {
- return mDecoratedBaseAdapter.getItemId(position);
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- return mDecoratedBaseAdapter.getView(position, convertView, parent);
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return mDecoratedBaseAdapter.areAllItemsEnabled();
- }
-
- @Override
- public View getDropDownView(final int position, final View convertView, final ViewGroup parent) {
- return mDecoratedBaseAdapter.getDropDownView(position, convertView, parent);
- }
-
- @Override
- public int getItemViewType(final int position) {
- return mDecoratedBaseAdapter.getItemViewType(position);
- }
-
- @Override
- public int getViewTypeCount() {
- return mDecoratedBaseAdapter.getViewTypeCount();
- }
-
- @Override
- public boolean hasStableIds() {
- return mDecoratedBaseAdapter.hasStableIds();
- }
-
- @Override
- public boolean isEmpty() {
- return mDecoratedBaseAdapter.isEmpty();
- }
-
- @Override
- public boolean isEnabled(final int position) {
- return mDecoratedBaseAdapter.isEnabled(position);
- }
-
- @Override
- public void notifyDataSetChanged() {
- if (!(mDecoratedBaseAdapter instanceof ArrayAdapter>)) {
- // fix #35 dirty trick !
- // leads to an infinite loop when trying because ArrayAdapter triggers notifyDataSetChanged itself
- mDecoratedBaseAdapter.notifyDataSetChanged();
- }
- }
-
- /**
- * Helper function if you want to force notifyDataSetChanged()
- */
- @SuppressWarnings("UnusedDeclaration")
- public void notifyDataSetChanged(final boolean force) {
- if (force || !(mDecoratedBaseAdapter instanceof ArrayAdapter>)) {
- // leads to an infinite loop when trying because ArrayAdapter triggers notifyDataSetChanged itself
- mDecoratedBaseAdapter.notifyDataSetChanged();
- }
- }
-
- @Override
- public void notifyDataSetInvalidated() {
- mDecoratedBaseAdapter.notifyDataSetInvalidated();
- }
-
- @Override
- public void registerDataSetObserver(final DataSetObserver observer) {
- mDecoratedBaseAdapter.registerDataSetObserver(observer);
- }
-
- @Override
- public void unregisterDataSetObserver(final DataSetObserver observer) {
- mDecoratedBaseAdapter.unregisterDataSetObserver(observer);
- }
-
- @Override
- public int getPositionForSection(final int section) {
- if (mDecoratedBaseAdapter instanceof SectionIndexer) {
- return ((SectionIndexer) mDecoratedBaseAdapter).getPositionForSection(section);
- }
- return 0;
- }
-
- @Override
- public int getSectionForPosition(final int position) {
- if (mDecoratedBaseAdapter instanceof SectionIndexer) {
- return ((SectionIndexer) mDecoratedBaseAdapter).getSectionForPosition(position);
- }
- return 0;
- }
-
- @Override
- public Object[] getSections() {
- if (mDecoratedBaseAdapter instanceof SectionIndexer) {
- return ((SectionIndexer) mDecoratedBaseAdapter).getSections();
- }
- return null;
- }
-
- public BaseAdapter getDecoratedBaseAdapter() {
- return mDecoratedBaseAdapter;
- }
-
- @Override
- public void swapItems(final int positionOne, final int positionTwo) {
- if (mDecoratedBaseAdapter instanceof Swappable) {
- ((Swappable) mDecoratedBaseAdapter).swapItems(positionOne, positionTwo);
- }
- }
-
- /**
- * If the adapter's list-view is hosted inside a parent(/grand-parent/etc) that can scroll horizontally, horizontal swipes won't
- * work, because the parent will prevent touch-events from reaching the list-view.
- *
- * Call this method with the value 'true' to fix this behavior.
- * Note that this will prevent the parent from scrolling horizontally when the user touches anywhere in a list-item.
- */
- public void setIsParentHorizontalScrollContainer(final boolean isParentHorizontalScrollContainer) {
- mIsParentHorizontalScrollContainer = isParentHorizontalScrollContainer;
- if (mListView instanceof DynamicListView) {
- DynamicListView dynListView = (DynamicListView) mListView;
- dynListView.setIsParentHorizontalScrollContainer(mIsParentHorizontalScrollContainer);
- }
- }
-
- public boolean isParentHorizontalScrollContainer() {
- return mIsParentHorizontalScrollContainer;
- }
-
- /**
- * If the adapter's list-view is hosted inside a parent(/grand-parent/etc) that can scroll horizontally, horizontal swipes won't
- * work, because the parent will prevent touch-events from reaching the list-view.
- *
- * If a list-item view has a child with the given resource-ID, the user can still swipe the list-item by touching that child.
- * If the user touches an area outside that child (but inside the list-item view), then the swipe will not happen and the parent
- * will do its job instead (scrolling horizontally).
- *
- * @param childResId The resource-ID of the list-items' child that the user should touch to be able to swipe the list-items.
- */
- public void setTouchChild(final int childResId) {
- mResIdTouchChild = childResId;
- if (mListView instanceof DynamicListView) {
- DynamicListView dynListView = (DynamicListView) mListView;
- dynListView.setDynamicTouchChild(mResIdTouchChild);
- }
- }
-
- public int getTouchChild() {
- return mResIdTouchChild;
- }
-}
\ No newline at end of file
diff --git a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/ListViewSetter.java b/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/ListViewSetter.java
deleted file mode 100644
index a4972ca..0000000
--- a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/ListViewSetter.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.nhaarman.listviewanimations;
-
-import android.widget.AbsListView;
-
-public interface ListViewSetter {
-
- void setAbsListView(AbsListView listView);
-}
diff --git a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/itemmanipulation/AnimateAdditionAdapter.java b/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/itemmanipulation/AnimateAdditionAdapter.java
deleted file mode 100644
index 91b5d23..0000000
--- a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/itemmanipulation/AnimateAdditionAdapter.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.nhaarman.listviewanimations.itemmanipulation;
-
-import android.util.Pair;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-
-import com.nhaarman.listviewanimations.BaseAdapterDecorator;
-import com.nineoldandroids.animation.Animator;
-import com.nineoldandroids.animation.AnimatorListenerAdapter;
-import com.nineoldandroids.animation.AnimatorSet;
-import com.nineoldandroids.animation.ObjectAnimator;
-import com.nineoldandroids.animation.ValueAnimator;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * An EXPERIMENTAL adapter for inserting rows into the {@link android.widget.ListView} with an animation. The root {@link BaseAdapter} should implement {@link Insertable},
- * otherwise an {@link java.lang.IllegalArgumentException} is thrown. This class only works with an instance of {@code ListView}!
- *
- * Usage:
- * - Wrap a new instance of this class around a {@link android.widget.BaseAdapter}.
- * - Set a {@code ListView} to this class using {@link #setListView(android.widget.ListView)}.
- * - Call {@link com.nhaarman.listviewanimations.itemmanipulation.AnimateAdditionAdapter#insert(int, Object)} to animate the addition of an item.
- *
- * Extend this class and override {@link com.nhaarman.listviewanimations.itemmanipulation.AnimateAdditionAdapter#getAdditionalAnimators(android.view.View,
- * android.view.ViewGroup)} to provide extra {@link com.nineoldandroids.animation.Animator}s.
- */
-@SuppressWarnings("unchecked")
-public class AnimateAdditionAdapter extends BaseAdapterDecorator {
-
- private static final long DEFAULT_SCROLLDOWN_ANIMATION_MS = 300;
- private static final long DEFAULT_INSERTION_ANIMATION_MS = 300;
- private static final String ALPHA = "alpha";
-
- private final Insertable mInsertable;
- private final InsertQueue mInsertQueue;
-
- private boolean mShouldAnimateDown = true;
-
- private long mInsertionAnimationDurationMs = DEFAULT_INSERTION_ANIMATION_MS;
- private long mScrolldownAnimationDurationMs = DEFAULT_SCROLLDOWN_ANIMATION_MS;
-
- /**
- * Create a new {@link com.nhaarman.listviewanimations.itemmanipulation.AnimateAdditionAdapter} with given {@link android.widget.BaseAdapter}.
- *
- * @param baseAdapter should implement {@link com.nhaarman.listviewanimations.itemmanipulation.AnimateAdditionAdapter.Insertable},
- * or be a {@link com.nhaarman.listviewanimations.BaseAdapterDecorator} whose BaseAdapter implements the interface.
- */
- public AnimateAdditionAdapter(final BaseAdapter baseAdapter) {
- super(baseAdapter);
-
- BaseAdapter rootAdapter = getRootAdapter();
- if (!(rootAdapter instanceof Insertable)) {
- throw new IllegalArgumentException("BaseAdapter should implement Insertable!");
- }
-
- mInsertable = (Insertable) rootAdapter;
- mInsertQueue = new InsertQueue(mInsertable);
- }
-
- private BaseAdapter getRootAdapter() {
- BaseAdapter adapter = getDecoratedBaseAdapter();
- while (adapter instanceof BaseAdapterDecorator) {
- adapter = ((BaseAdapterDecorator) adapter).getDecoratedBaseAdapter();
- }
-
- return adapter;
- }
-
- @Override
- @Deprecated
- /**
- * @deprecated AnimateAdditionAdapter requires a ListView instance. Use {@link #setListView(android.widget.ListView)} instead.
- */
- public void setAbsListView(final AbsListView listView) {
- if (!(listView instanceof ListView)) {
- throw new IllegalArgumentException("AnimateAdditionAdapter requires a ListView instance!");
- }
- super.setAbsListView(listView);
- }
-
- public void setListView(final ListView listView) {
- super.setAbsListView(listView);
- }
-
- /**
- * Set whether the list should animate downwards when items are added above the first visible item.
- * @param shouldAnimateDown defaults to {@code true}.
- */
- @SuppressWarnings("UnusedDeclaration")
- public void setShouldAnimateDown(final boolean shouldAnimateDown) {
- mShouldAnimateDown = shouldAnimateDown;
- }
-
- /**
- * Set the duration of the scrolldown animation per item for when items are inserted above the first visible item.
- * @param scrolldownAnimationDurationMs the duration in ms.
- */
- @SuppressWarnings("UnusedDeclaration")
- public void setScrolldownAnimationDuration(final long scrolldownAnimationDurationMs) {
- mScrolldownAnimationDurationMs = scrolldownAnimationDurationMs;
- }
-
- /**
- * Set the duration of the insertion animation.
- * @param insertionAnimationDurationMs the duration in ms.
- */
- @SuppressWarnings("UnusedDeclaration")
- public void setInsertionAnimationDuration(final long insertionAnimationDurationMs) {
- mInsertionAnimationDurationMs = insertionAnimationDurationMs;
- }
-
- /**
- * Insert an item at given index. Will show an entrance animation for the new item if the newly added item is visible.
- * Will also call {@link Insertable#add(int, Object)} of the root {@link BaseAdapter}.
- *
- * @param index the index the new item should be inserted at
- * @param item the item to insert
- */
- public void insert(final int index, final T item) {
- insert(new Pair(index, item));
- }
-
- /**
- * Insert items at given indexes. Will show an entrance animation for the new items if the newly added item is visible.
- * Will also call {@link Insertable#add(int, Object)} of the root {@link BaseAdapter}.
- *
- * @param indexItemPairs the index-item pairs to insert. The first argument of the {@code Pair} is the index, the second argument is the item.
- */
- public void insert(final Pair... indexItemPairs) {
- insert(Arrays.asList(indexItemPairs));
- }
-
- /**
- * Insert items at given indexes. Will show an entrance animation for the new items if the newly added item is visible.
- * Will also call {@link Insertable#add(int, Object)} of the root {@link BaseAdapter}.
- *
- * @param indexItemPairs the index-item pairs to insert. The first argument of the {@code Pair} is the index, the second argument is the item.
- */
- public void insert(final List> indexItemPairs) {
- List> visibleViews = new ArrayList>();
- List insertedPositions = new ArrayList();
- List insertedBelowPositions = new ArrayList();
-
- int scrollDistance = 0;
- int numInsertedAbove = 0;
-
- for (Pair pair : indexItemPairs) {
- if (getAbsListView().getFirstVisiblePosition() > pair.first) {
- /* Inserting an item above the first visible position */
- int index = pair.first;
-
- /* Correct the index for already inserted positions above the first visible view. */
- for (int insertedPosition : insertedPositions) {
- if (index >= insertedPosition) {
- index++;
- }
- }
-
- mInsertable.add(index, pair.second);
- insertedPositions.add(index);
- numInsertedAbove++;
-
- if (mShouldAnimateDown) {
- View view = getView(pair.first, null, getAbsListView());
- view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
- scrollDistance -= view.getMeasuredHeight();
- }
- } else if (getAbsListView().getLastVisiblePosition() >= pair.first) {
- /* Inserting an item that becomes visible on screen */
- int index = pair.first;
-
- /* Correct the index for already inserted positions above the first visible view */
- for (int insertedPosition : insertedPositions) {
- if (index >= insertedPosition) {
- index++;
- }
- }
- Pair newPair = new Pair(index, pair.second);
- visibleViews.add(newPair);
- } else {
- /* Inserting an item below the last visible item */
- int index = pair.first;
-
- /* Correct the index for already inserted positions above the first visible view */
- for (int insertedPosition : insertedPositions) {
- if (index >= insertedPosition) {
- index++;
- }
- }
-
- /* Correct the index for already inserted positions below the last visible view */
- for (int queuedPosition : insertedBelowPositions) {
- if (index >= queuedPosition) {
- index++;
- }
- }
-
- insertedBelowPositions.add(index);
- mInsertable.add(index, pair.second);
- }
- }
-
- if (mShouldAnimateDown) {
- getAbsListView().smoothScrollBy(scrollDistance, (int) (mScrolldownAnimationDurationMs * numInsertedAbove));
- }
-
- mInsertQueue.insert(visibleViews);
- ((ListView) getAbsListView()).setSelectionFromTop(getAbsListView().getFirstVisiblePosition() + numInsertedAbove, getAbsListView().getChildAt(0).getTop());
- }
-
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- final View view = super.getView(position, convertView, parent);
-
- if (mInsertQueue.getActiveIndexes().contains(position)) {
- int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(ViewGroup.LayoutParams.MATCH_PARENT, View.MeasureSpec.AT_MOST);
- int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(ViewGroup.LayoutParams.WRAP_CONTENT, View.MeasureSpec.AT_MOST);
- view.measure(widthMeasureSpec, heightMeasureSpec);
-
- int originalHeight = view.getMeasuredHeight();
-
- ValueAnimator heightAnimator = ValueAnimator.ofInt(1, originalHeight);
- heightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(final ValueAnimator animation) {
- ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
- layoutParams.height = (Integer) animation.getAnimatedValue();
- view.setLayoutParams(layoutParams);
- }
- });
-
- ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(view, ALPHA, 0, 1);
-
- AnimatorSet animatorSet = new AnimatorSet();
- Animator[] customAnimators = getAdditionalAnimators(view, parent);
- Animator[] animators = new Animator[customAnimators.length + 2];
- animators[0] = heightAnimator;
- animators[1] = alphaAnimator;
- System.arraycopy(customAnimators, 0, animators, 2, customAnimators.length);
- animatorSet.playTogether(animators);
- animatorSet.setDuration(mInsertionAnimationDurationMs);
- animatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- mInsertQueue.removeActiveIndex(position);
- }
- });
- animatorSet.start();
- }
-
- return view;
- }
-
- /**
- * Override this method to provide additional animators on top of the default height and alpha animation.
- *
- * @param view The {@link View} that will get animated.
- * @param parent The parent that this view will eventually be attached to.
- * @return a non-null array of Animators.
- */
- @SuppressWarnings("UnusedParameters")
- protected Animator[] getAdditionalAnimators(final View view, final ViewGroup parent) {
- return new Animator[]{};
- }
-
- /**
- * An interface for inserting items at a certain index.
- */
- public interface Insertable {
-
- /**
- * Will be called to insert given {@code item} at given {@code index} in the list.
- *
- * @param index the index the new item should be inserted at
- * @param item the item to insert
- */
- public void add(int index, T item);
- }
-}
diff --git a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/itemmanipulation/AnimateDismissAdapter.java b/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/itemmanipulation/AnimateDismissAdapter.java
deleted file mode 100644
index 1bd84bd..0000000
--- a/listview-animations-demo/ListviewAnimationLib/src/com/nhaarman/listviewanimations/itemmanipulation/AnimateDismissAdapter.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright 2013 Niek Haarman
- *
- * 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 com.nhaarman.listviewanimations.itemmanipulation;
-
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-import com.nhaarman.listviewanimations.BaseAdapterDecorator;
-import com.nhaarman.listviewanimations.util.AdapterViewUtil;
-import com.nineoldandroids.animation.Animator;
-import com.nineoldandroids.animation.AnimatorListenerAdapter;
-import com.nineoldandroids.animation.AnimatorSet;
-import com.nineoldandroids.animation.ValueAnimator;
-import com.nineoldandroids.animation.ValueAnimator.AnimatorUpdateListener;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A {@link BaseAdapterDecorator} class that provides animations to the removal
- * of items in the given {@link BaseAdapter}.
- */
-public class AnimateDismissAdapter extends BaseAdapterDecorator {
-
- private final OnDismissCallback mCallback;
-
- /**
- * Create a new AnimateDismissAdapter based on the given {@link BaseAdapter}.
- *
- * @param callback
- * The {@link OnDismissCallback} to trigger when the user has
- * indicated that she would like to dismiss one or more list
- * items.
- */
- public AnimateDismissAdapter(final BaseAdapter baseAdapter, final OnDismissCallback callback) {
- super(baseAdapter);
- mCallback = callback;
- }
-
- /**
- * Animate dismissal of the item at given position.
- */
- @SuppressWarnings("UnusedDeclaration")
- public void animateDismiss(final int position) {
- animateDismiss(Arrays.asList(position));
- }
-
- /**
- * Animate dismissal of the items at given positions.
- */
- public void animateDismiss(final Collection positions) {
- final List positionsCopy = new ArrayList(positions);
- if (getAbsListView() == null) {
- throw new IllegalStateException("Call setAbsListView() on this AnimateDismissAdapter before calling setAdapter()!");
- }
-
- List views = getVisibleViewsForPositions(positionsCopy);
-
- if (!views.isEmpty()) {
- List animators = new ArrayList();
- for (final View view : views) {
- animators.add(createAnimatorForView(view));
- }
-
- AnimatorSet animatorSet = new AnimatorSet();
-
- Animator[] animatorsArray = new Animator[animators.size()];
- for (int i = 0; i < animatorsArray.length; i++) {
- animatorsArray[i] = animators.get(i);
- }
-
- animatorSet.playTogether(animatorsArray);
- animatorSet.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(final Animator animator) {
- invokeCallback(positionsCopy);
- }
- });
- animatorSet.start();
- } else {
- invokeCallback(positionsCopy);
- }
- }
-
- private void invokeCallback(final Collection positions) {
- ArrayList positionsList = new ArrayList