(v);
+ mCachedItemViews.addLast(ref);
+ }
+ }
+
+ /**
+ * Transforms MAC address in 00:11:22:33:44:55 format to byte array representation
+ * @param MAC
+ * @return
+ */
+ public static byte[] MACtobyteConverter(String MAC) {
+ // first remove all ":" from MAC address
+ MAC = MAC.replaceAll(":", "");
+ Log.d("ComponentLibrary.ToolBox", "MACtobyteConverter input ="+MAC);
+
+ // now convert to byte array
+ int len = MAC.length();
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(MAC.charAt(i), 16) << 4)
+ + Character.digit(MAC.charAt(i+1), 16));
+ }
+ return data;
+ }
+
+ /**
+ * Transforms MAC address in 00:11:22:33:44:55 format to byte array representation
+ * @param MAC
+ * @return
+ */
+ public static byte[] IMEItobyteConverter(String IMEI) {
+ // now convert to byte array
+ long imeiInLong;
+ try {
+ imeiInLong = Long.parseLong(IMEI);
+ }
+ catch (NumberFormatException e) {
+ Log.w(TAG, "Can't convert IMEI to byte, Illegal number format");
+ return null;
+ }
+ byte[] data = ByteBuffer.allocate(8).putLong(imeiInLong).array();
+ return data;
+ }
+
+ public static String formatISODate(String isoDate){
+ SimpleDateFormat inFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",Locale.US);
+ Date dtIn;
+ try {
+ dtIn = inFormat.parse(isoDate); //where dateString is a date in ISO-8601 format
+ SimpleDateFormat outFormat = new SimpleDateFormat("dd.MM.yyyy",Locale.US);
+ return outFormat.format(dtIn);
+ } catch (ParseException e) {
+ Log.e(TAG, "Parse date error",e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Parse NullPointerException error",e);
+ }
+ return isoDate;
+ }
+}
+
diff --git a/MAComponents/src/com/martinappl/components/general/Validate.java b/lib/src/main/java/it/moondroid/coverflow/components/general/Validate.java
similarity index 97%
rename from MAComponents/src/com/martinappl/components/general/Validate.java
rename to lib/src/main/java/it/moondroid/coverflow/components/general/Validate.java
index 26cfc6c..b0b4d98 100644
--- a/MAComponents/src/com/martinappl/components/general/Validate.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/general/Validate.java
@@ -1,507 +1,507 @@
-package com.martinappl.components.general;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * This class assists in validating arguments.
- *
- * The class is based along the lines of JUnit. If an argument value is
- * deemed invalid, an IllegalArgumentException is thrown. For example:
- *
- *
- * Validate.isTrue( i > 0, "The value must be greater than zero: ", i);
- * Validate.notNull( surname, "The surname must not be null");
- *
- *
- * @author Apache Software Foundation
- * @author Ola Berg
- * @author Gary Gregory
- * @author Norm Deane
- * @since 2.0
- * @version $Id: Validate.java 1057051 2011-01-09 23:15:51Z sebb $
- */
-public class Validate {
- // Validate has no dependencies on other classes in Commons Lang at present
-
- /**
- * Constructor. This class should not normally be instantiated.
- */
- public Validate() {
- super();
- }
-
- // isTrue
- //---------------------------------------------------------------------------------
- /**
- * Validate that the argument condition is true; otherwise
- * throwing an exception with the specified message. This method is useful when
- * validating according to an arbitrary boolean expression, such as validating an
- * object or using your own custom validation expression.
- *
- * Validate.isTrue( myObject.isOk(), "The object is not OK: ", myObject);
- *
- * For performance reasons, the object value is passed as a separate parameter and
- * appended to the exception message only in the case of an error.
- *
- * @param expression the boolean expression to check
- * @param message the exception message if invalid
- * @param value the value to append to the message when invalid
- * @throws IllegalArgumentException if expression is false
- */
- public static void isTrue(boolean expression, String message, Object value) {
- if (expression == false) {
- throw new IllegalArgumentException(message + value);
- }
- }
-
- /**
- * Validate that the argument condition is true; otherwise
- * throwing an exception with the specified message. This method is useful when
- * validating according to an arbitrary boolean expression, such as validating a
- * primitive number or using your own custom validation expression.
- *
- * Validate.isTrue(i > 0.0, "The value must be greater than zero: ", i);
- *
- * For performance reasons, the long value is passed as a separate parameter and
- * appended to the exception message only in the case of an error.
- *
- * @param expression the boolean expression to check
- * @param message the exception message if invalid
- * @param value the value to append to the message when invalid
- * @throws IllegalArgumentException if expression is false
- */
- public static void isTrue(boolean expression, String message, long value) {
- if (expression == false) {
- throw new IllegalArgumentException(message + value);
- }
- }
-
- /**
- * Validate that the argument condition is true; otherwise
- * throwing an exception with the specified message. This method is useful when
- * validating according to an arbitrary boolean expression, such as validating a
- * primitive number or using your own custom validation expression.
- *
- * Validate.isTrue(d > 0.0, "The value must be greater than zero: ", d);
- *
- * For performance reasons, the double value is passed as a separate parameter and
- * appended to the exception message only in the case of an error.
- *
- * @param expression the boolean expression to check
- * @param message the exception message if invalid
- * @param value the value to append to the message when invalid
- * @throws IllegalArgumentException if expression is false
- */
- public static void isTrue(boolean expression, String message, double value) {
- if (expression == false) {
- throw new IllegalArgumentException(message + value);
- }
- }
-
- /**
- * Validate that the argument condition is true; otherwise
- * throwing an exception with the specified message. This method is useful when
- * validating according to an arbitrary boolean expression, such as validating a
- * primitive number or using your own custom validation expression.
- *
- *
- * Validate.isTrue( (i > 0), "The value must be greater than zero");
- * Validate.isTrue( myObject.isOk(), "The object is not OK");
- *
- *
- * @param expression the boolean expression to check
- * @param message the exception message if invalid
- * @throws IllegalArgumentException if expression is false
- */
- public static void isTrue(boolean expression, String message) {
- if (expression == false) {
- throw new IllegalArgumentException(message);
- }
- }
-
- /**
- * Validate that the argument condition is true; otherwise
- * throwing an exception. This method is useful when validating according
- * to an arbitrary boolean expression, such as validating a
- * primitive number or using your own custom validation expression.
- *
- *
- * Validate.isTrue(i > 0);
- * Validate.isTrue(myObject.isOk());
- *
- * The message of the exception is "The validated expression is
- * false".
- *
- * @param expression the boolean expression to check
- * @throws IllegalArgumentException if expression is false
- */
- public static void isTrue(boolean expression) {
- if (expression == false) {
- throw new IllegalArgumentException("The validated expression is false");
- }
- }
-
- // notNull
- //---------------------------------------------------------------------------------
-
- /**
- * Validate that the specified argument is not null;
- * otherwise throwing an exception.
- *
- *
Validate.notNull(myObject);
- *
- * The message of the exception is "The validated object is
- * null".
- *
- * @param object the object to check
- * @throws IllegalArgumentException if the object is null
- */
- public static void notNull(Object object) {
- notNull(object, "The validated object is null");
- }
-
- /**
- * Validate that the specified argument is not null;
- * otherwise throwing an exception with the specified message.
- *
- *
Validate.notNull(myObject, "The object must not be null");
- *
- * @param object the object to check
- * @param message the exception message if invalid
- */
- public static void notNull(Object object, String message) {
- if (object == null) {
- throw new IllegalArgumentException(message);
- }
- }
-
- // notEmpty array
- //---------------------------------------------------------------------------------
-
- /**
- * Validate that the specified argument array is neither null
- * nor a length of zero (no elements); otherwise throwing an exception
- * with the specified message.
- *
- *
Validate.notEmpty(myArray, "The array must not be empty");
- *
- * @param array the array to check
- * @param message the exception message if invalid
- * @throws IllegalArgumentException if the array is empty
- */
- public static void notEmpty(Object[] array, String message) {
- if (array == null || array.length == 0) {
- throw new IllegalArgumentException(message);
- }
- }
-
- /**
- * Validate that the specified argument array is neither null
- * nor a length of zero (no elements); otherwise throwing an exception.
- *
- *
Validate.notEmpty(myArray);
- *
- * The message in the exception is "The validated array is
- * empty".
- *
- * @param array the array to check
- * @throws IllegalArgumentException if the array is empty
- */
- public static void notEmpty(Object[] array) {
- notEmpty(array, "The validated array is empty");
- }
-
- // notEmpty collection
- //---------------------------------------------------------------------------------
-
- /**
- *
Validate that the specified argument collection is neither null
- * nor a size of zero (no elements); otherwise throwing an exception
- * with the specified message.
- *
- *
Validate.notEmpty(myCollection, "The collection must not be empty");
- *
- * @param collection the collection to check
- * @param message the exception message if invalid
- * @throws IllegalArgumentException if the collection is empty
- */
- public static void notEmpty(Collection collection, String message) {
- if (collection == null || collection.size() == 0) {
- throw new IllegalArgumentException(message);
- }
- }
-
- /**
- * Validate that the specified argument collection is neither null
- * nor a size of zero (no elements); otherwise throwing an exception.
- *
- *
Validate.notEmpty(myCollection);
- *
- * The message in the exception is "The validated collection is
- * empty".
- *
- * @param collection the collection to check
- * @throws IllegalArgumentException if the collection is empty
- */
- public static void notEmpty(Collection collection) {
- notEmpty(collection, "The validated collection is empty");
- }
-
- // notEmpty map
- //---------------------------------------------------------------------------------
-
- /**
- * Validate that the specified argument map is neither null
- * nor a size of zero (no elements); otherwise throwing an exception
- * with the specified message.
- *
- *
Validate.notEmpty(myMap, "The map must not be empty");
- *
- * @param map the map to check
- * @param message the exception message if invalid
- * @throws IllegalArgumentException if the map is empty
- */
- public static void notEmpty(Map map, String message) {
- if (map == null || map.size() == 0) {
- throw new IllegalArgumentException(message);
- }
- }
-
- /**
- * Validate that the specified argument map is neither null
- * nor a size of zero (no elements); otherwise throwing an exception.
- *
- *
Validate.notEmpty(myMap);
- *
- * The message in the exception is "The validated map is
- * empty".
- *
- * @param map the map to check
- * @throws IllegalArgumentException if the map is empty
- * @see #notEmpty(Map, String)
- */
- public static void notEmpty(Map map) {
- notEmpty(map, "The validated map is empty");
- }
-
- // notEmpty string
- //---------------------------------------------------------------------------------
-
- /**
- * Validate that the specified argument string is
- * neither null nor a length of zero (no characters);
- * otherwise throwing an exception with the specified message.
- *
- *
Validate.notEmpty(myString, "The string must not be empty");
- *
- * @param string the string to check
- * @param message the exception message if invalid
- * @throws IllegalArgumentException if the string is empty
- */
- public static void notEmpty(String string, String message) {
- if (string == null || string.length() == 0) {
- throw new IllegalArgumentException(message);
- }
- }
-
- /**
- * Validate that the specified argument string is
- * neither null nor a length of zero (no characters);
- * otherwise throwing an exception with the specified message.
- *
- *
Validate.notEmpty(myString);
- *
- * The message in the exception is "The validated
- * string is empty".
- *
- * @param string the string to check
- * @throws IllegalArgumentException if the string is empty
- */
- public static void notEmpty(String string) {
- notEmpty(string, "The validated string is empty");
- }
-
- // notNullElements array
- //---------------------------------------------------------------------------------
-
- /**
- * Validate that the specified argument array is neither
- * null nor contains any elements that are null;
- * otherwise throwing an exception with the specified message.
- *
- *
Validate.noNullElements(myArray, "The array contain null at position %d");
- *
- * If the array is null, then the message in the exception
- * is "The validated object is null".
- *
- * @param array the array to check
- * @param message the exception message if the collection has null elements
- * @throws IllegalArgumentException if the array is null or
- * an element in the array is null
- */
- public static void noNullElements(Object[] array, String message) {
- Validate.notNull(array);
- for (int i = 0; i < array.length; i++) {
- if (array[i] == null) {
- throw new IllegalArgumentException(message);
- }
- }
- }
-
- /**
- * Validate that the specified argument array is neither
- * null nor contains any elements that are null;
- * otherwise throwing an exception.
- *
- *
Validate.noNullElements(myArray);
- *
- * If the array is null, then the message in the exception
- * is "The validated object is null".
- *
- * If the array has a null element, then the message in the
- * exception is "The validated array contains null element at index:
- * " followed by the index.
- *
- * @param array the array to check
- * @throws IllegalArgumentException if the array is null or
- * an element in the array is null
- */
- public static void noNullElements(Object[] array) {
- Validate.notNull(array);
- for (int i = 0; i < array.length; i++) {
- if (array[i] == null) {
- throw new IllegalArgumentException("The validated array contains null element at index: " + i);
- }
- }
- }
-
- // notNullElements collection
- //---------------------------------------------------------------------------------
-
- /**
- * Validate that the specified argument collection is neither
- * null nor contains any elements that are null;
- * otherwise throwing an exception with the specified message.
- *
- *
Validate.noNullElements(myCollection, "The collection contains null elements");
- *
- * If the collection is null, then the message in the exception
- * is "The validated object is null".
- *
- *
- * @param collection the collection to check
- * @param message the exception message if the collection has
- * @throws IllegalArgumentException if the collection is null or
- * an element in the collection is null
- */
- public static void noNullElements(Collection collection, String message) {
- Validate.notNull(collection);
- for (Iterator it = collection.iterator(); it.hasNext();) {
- if (it.next() == null) {
- throw new IllegalArgumentException(message);
- }
- }
- }
-
- /**
- * Validate that the specified argument collection is neither
- * null nor contains any elements that are null;
- * otherwise throwing an exception.
- *
- *
Validate.noNullElements(myCollection);
- *
- * If the collection is null, then the message in the exception
- * is "The validated object is null".
- *
- * If the collection has a null element, then the message in the
- * exception is "The validated collection contains null element at index:
- * " followed by the index.
- *
- * @param collection the collection to check
- * @throws IllegalArgumentException if the collection is null or
- * an element in the collection is null
- */
- public static void noNullElements(Collection collection) {
- Validate.notNull(collection);
- int i = 0;
- for (Iterator it = collection.iterator(); it.hasNext(); i++) {
- if (it.next() == null) {
- throw new IllegalArgumentException("The validated collection contains null element at index: " + i);
- }
- }
- }
-
- /**
- * Validate an argument, throwing IllegalArgumentException
- * if the argument collection is null or has elements that
- * are not of type clazz or a subclass.
- *
- *
- * Validate.allElementsOfType(collection, String.class, "Collection has invalid elements");
- *
- *
- * @param collection the collection to check, not null
- * @param clazz the Class which the collection's elements are expected to be, not null
- * @param message the exception message if the Collection has elements not of type clazz
- * @since 2.1
- */
- public static void allElementsOfType(Collection collection, Class clazz, String message) {
- Validate.notNull(collection);
- Validate.notNull(clazz);
- for (Iterator it = collection.iterator(); it.hasNext(); ) {
- if (clazz.isInstance(it.next()) == false) {
- throw new IllegalArgumentException(message);
- }
- }
- }
-
- /**
- *
- * Validate an argument, throwing IllegalArgumentException if the argument collection is
- * null or has elements that are not of type clazz or a subclass.
- *
- *
- *
- * Validate.allElementsOfType(collection, String.class);
- *
- *
- *
- * The message in the exception is 'The validated collection contains an element not of type clazz at index: '.
- *
- *
- * @param collection the collection to check, not null
- * @param clazz the Class which the collection's elements are expected to be, not null
- * @since 2.1
- */
- public static void allElementsOfType(Collection collection, Class clazz) {
- Validate.notNull(collection);
- Validate.notNull(clazz);
- int i = 0;
- for (Iterator it = collection.iterator(); it.hasNext(); i++) {
- if (clazz.isInstance(it.next()) == false) {
- throw new IllegalArgumentException("The validated collection contains an element not of type "
- + clazz.getName() + " at index: " + i);
- }
- }
- }
-
+package it.moondroid.coverflow.components.general;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * This class assists in validating arguments.
+ *
+ * The class is based along the lines of JUnit. If an argument value is
+ * deemed invalid, an IllegalArgumentException is thrown. For example:
+ *
+ *
+ * Validate.isTrue( i > 0, "The value must be greater than zero: ", i);
+ * Validate.notNull( surname, "The surname must not be null");
+ *
+ *
+ * @author Apache Software Foundation
+ * @author Ola Berg
+ * @author Gary Gregory
+ * @author Norm Deane
+ * @since 2.0
+ * @version $Id: Validate.java 1057051 2011-01-09 23:15:51Z sebb $
+ */
+public class Validate {
+ // Validate has no dependencies on other classes in Commons Lang at present
+
+ /**
+ * Constructor. This class should not normally be instantiated.
+ */
+ public Validate() {
+ super();
+ }
+
+ // isTrue
+ //---------------------------------------------------------------------------------
+ /**
+ * Validate that the argument condition is true; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating an
+ * object or using your own custom validation expression.
+ *
+ * Validate.isTrue( myObject.isOk(), "The object is not OK: ", myObject);
+ *
+ * For performance reasons, the object value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.
+ *
+ * @param expression the boolean expression to check
+ * @param message the exception message if invalid
+ * @param value the value to append to the message when invalid
+ * @throws IllegalArgumentException if expression is false
+ */
+ public static void isTrue(boolean expression, String message, Object value) {
+ if (expression == false) {
+ throw new IllegalArgumentException(message + value);
+ }
+ }
+
+ /**
+ * Validate that the argument condition is true; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ * Validate.isTrue(i > 0.0, "The value must be greater than zero: ", i);
+ *
+ * For performance reasons, the long value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.
+ *
+ * @param expression the boolean expression to check
+ * @param message the exception message if invalid
+ * @param value the value to append to the message when invalid
+ * @throws IllegalArgumentException if expression is false
+ */
+ public static void isTrue(boolean expression, String message, long value) {
+ if (expression == false) {
+ throw new IllegalArgumentException(message + value);
+ }
+ }
+
+ /**
+ * Validate that the argument condition is true; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ * Validate.isTrue(d > 0.0, "The value must be greater than zero: ", d);
+ *
+ * For performance reasons, the double value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.
+ *
+ * @param expression the boolean expression to check
+ * @param message the exception message if invalid
+ * @param value the value to append to the message when invalid
+ * @throws IllegalArgumentException if expression is false
+ */
+ public static void isTrue(boolean expression, String message, double value) {
+ if (expression == false) {
+ throw new IllegalArgumentException(message + value);
+ }
+ }
+
+ /**
+ * Validate that the argument condition is true; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ *
+ * Validate.isTrue( (i > 0), "The value must be greater than zero");
+ * Validate.isTrue( myObject.isOk(), "The object is not OK");
+ *
+ *
+ * @param expression the boolean expression to check
+ * @param message the exception message if invalid
+ * @throws IllegalArgumentException if expression is false
+ */
+ public static void isTrue(boolean expression, String message) {
+ if (expression == false) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Validate that the argument condition is true; otherwise
+ * throwing an exception. This method is useful when validating according
+ * to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ *
+ * Validate.isTrue(i > 0);
+ * Validate.isTrue(myObject.isOk());
+ *
+ * The message of the exception is "The validated expression is
+ * false".
+ *
+ * @param expression the boolean expression to check
+ * @throws IllegalArgumentException if expression is false
+ */
+ public static void isTrue(boolean expression) {
+ if (expression == false) {
+ throw new IllegalArgumentException("The validated expression is false");
+ }
+ }
+
+ // notNull
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument is not null;
+ * otherwise throwing an exception.
+ *
+ *
Validate.notNull(myObject);
+ *
+ * The message of the exception is "The validated object is
+ * null".
+ *
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is null
+ */
+ public static void notNull(Object object) {
+ notNull(object, "The validated object is null");
+ }
+
+ /**
+ * Validate that the specified argument is not null;
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.notNull(myObject, "The object must not be null");
+ *
+ * @param object the object to check
+ * @param message the exception message if invalid
+ */
+ public static void notNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ // notEmpty array
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument array is neither null
+ * nor a length of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ *
Validate.notEmpty(myArray, "The array must not be empty");
+ *
+ * @param array the array to check
+ * @param message the exception message if invalid
+ * @throws IllegalArgumentException if the array is empty
+ */
+ public static void notEmpty(Object[] array, String message) {
+ if (array == null || array.length == 0) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Validate that the specified argument array is neither null
+ * nor a length of zero (no elements); otherwise throwing an exception.
+ *
+ *
Validate.notEmpty(myArray);
+ *
+ * The message in the exception is "The validated array is
+ * empty".
+ *
+ * @param array the array to check
+ * @throws IllegalArgumentException if the array is empty
+ */
+ public static void notEmpty(Object[] array) {
+ notEmpty(array, "The validated array is empty");
+ }
+
+ // notEmpty collection
+ //---------------------------------------------------------------------------------
+
+ /**
+ *
Validate that the specified argument collection is neither null
+ * nor a size of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ *
Validate.notEmpty(myCollection, "The collection must not be empty");
+ *
+ * @param collection the collection to check
+ * @param message the exception message if invalid
+ * @throws IllegalArgumentException if the collection is empty
+ */
+ public static void notEmpty(Collection collection, String message) {
+ if (collection == null || collection.size() == 0) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Validate that the specified argument collection is neither null
+ * nor a size of zero (no elements); otherwise throwing an exception.
+ *
+ *
Validate.notEmpty(myCollection);
+ *
+ * The message in the exception is "The validated collection is
+ * empty".
+ *
+ * @param collection the collection to check
+ * @throws IllegalArgumentException if the collection is empty
+ */
+ public static void notEmpty(Collection collection) {
+ notEmpty(collection, "The validated collection is empty");
+ }
+
+ // notEmpty map
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument map is neither null
+ * nor a size of zero (no elements); otherwise throwing an exception
+ * with the specified message.
+ *
+ *
Validate.notEmpty(myMap, "The map must not be empty");
+ *
+ * @param map the map to check
+ * @param message the exception message if invalid
+ * @throws IllegalArgumentException if the map is empty
+ */
+ public static void notEmpty(Map map, String message) {
+ if (map == null || map.size() == 0) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Validate that the specified argument map is neither null
+ * nor a size of zero (no elements); otherwise throwing an exception.
+ *
+ *
Validate.notEmpty(myMap);
+ *
+ * The message in the exception is "The validated map is
+ * empty".
+ *
+ * @param map the map to check
+ * @throws IllegalArgumentException if the map is empty
+ * @see #notEmpty(java.util.Map, String)
+ */
+ public static void notEmpty(Map map) {
+ notEmpty(map, "The validated map is empty");
+ }
+
+ // notEmpty string
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument string is
+ * neither null nor a length of zero (no characters);
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.notEmpty(myString, "The string must not be empty");
+ *
+ * @param string the string to check
+ * @param message the exception message if invalid
+ * @throws IllegalArgumentException if the string is empty
+ */
+ public static void notEmpty(String string, String message) {
+ if (string == null || string.length() == 0) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Validate that the specified argument string is
+ * neither null nor a length of zero (no characters);
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.notEmpty(myString);
+ *
+ * The message in the exception is "The validated
+ * string is empty".
+ *
+ * @param string the string to check
+ * @throws IllegalArgumentException if the string is empty
+ */
+ public static void notEmpty(String string) {
+ notEmpty(string, "The validated string is empty");
+ }
+
+ // notNullElements array
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument array is neither
+ * null nor contains any elements that are null;
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.noNullElements(myArray, "The array contain null at position %d");
+ *
+ * If the array is null, then the message in the exception
+ * is "The validated object is null".
+ *
+ * @param array the array to check
+ * @param message the exception message if the collection has null elements
+ * @throws IllegalArgumentException if the array is null or
+ * an element in the array is null
+ */
+ public static void noNullElements(Object[] array, String message) {
+ Validate.notNull(array);
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+
+ /**
+ * Validate that the specified argument array is neither
+ * null nor contains any elements that are null;
+ * otherwise throwing an exception.
+ *
+ *
Validate.noNullElements(myArray);
+ *
+ * If the array is null, then the message in the exception
+ * is "The validated object is null".
+ *
+ * If the array has a null element, then the message in the
+ * exception is "The validated array contains null element at index:
+ * " followed by the index.
+ *
+ * @param array the array to check
+ * @throws IllegalArgumentException if the array is null or
+ * an element in the array is null
+ */
+ public static void noNullElements(Object[] array) {
+ Validate.notNull(array);
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ throw new IllegalArgumentException("The validated array contains null element at index: " + i);
+ }
+ }
+ }
+
+ // notNullElements collection
+ //---------------------------------------------------------------------------------
+
+ /**
+ * Validate that the specified argument collection is neither
+ * null nor contains any elements that are null;
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.noNullElements(myCollection, "The collection contains null elements");
+ *
+ * If the collection is null, then the message in the exception
+ * is "The validated object is null".
+ *
+ *
+ * @param collection the collection to check
+ * @param message the exception message if the collection has
+ * @throws IllegalArgumentException if the collection is null or
+ * an element in the collection is null
+ */
+ public static void noNullElements(Collection collection, String message) {
+ Validate.notNull(collection);
+ for (Iterator it = collection.iterator(); it.hasNext();) {
+ if (it.next() == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+
+ /**
+ * Validate that the specified argument collection is neither
+ * null nor contains any elements that are null;
+ * otherwise throwing an exception.
+ *
+ *
Validate.noNullElements(myCollection);
+ *
+ * If the collection is null, then the message in the exception
+ * is "The validated object is null".
+ *
+ * If the collection has a null element, then the message in the
+ * exception is "The validated collection contains null element at index:
+ * " followed by the index.
+ *
+ * @param collection the collection to check
+ * @throws IllegalArgumentException if the collection is null or
+ * an element in the collection is null
+ */
+ public static void noNullElements(Collection collection) {
+ Validate.notNull(collection);
+ int i = 0;
+ for (Iterator it = collection.iterator(); it.hasNext(); i++) {
+ if (it.next() == null) {
+ throw new IllegalArgumentException("The validated collection contains null element at index: " + i);
+ }
+ }
+ }
+
+ /**
+ * Validate an argument, throwing IllegalArgumentException
+ * if the argument collection is null or has elements that
+ * are not of type clazz or a subclass.
+ *
+ *
+ * Validate.allElementsOfType(collection, String.class, "Collection has invalid elements");
+ *
+ *
+ * @param collection the collection to check, not null
+ * @param clazz the Class which the collection's elements are expected to be, not null
+ * @param message the exception message if the Collection has elements not of type clazz
+ * @since 2.1
+ */
+ public static void allElementsOfType(Collection collection, Class clazz, String message) {
+ Validate.notNull(collection);
+ Validate.notNull(clazz);
+ for (Iterator it = collection.iterator(); it.hasNext(); ) {
+ if (clazz.isInstance(it.next()) == false) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+
+ /**
+ *
+ * Validate an argument, throwing IllegalArgumentException if the argument collection is
+ * null or has elements that are not of type clazz or a subclass.
+ *
+ *
+ *
+ * Validate.allElementsOfType(collection, String.class);
+ *
+ *
+ *
+ * The message in the exception is 'The validated collection contains an element not of type clazz at index: '.
+ *
+ *
+ * @param collection the collection to check, not null
+ * @param clazz the Class which the collection's elements are expected to be, not null
+ * @since 2.1
+ */
+ public static void allElementsOfType(Collection collection, Class clazz) {
+ Validate.notNull(collection);
+ Validate.notNull(clazz);
+ int i = 0;
+ for (Iterator it = collection.iterator(); it.hasNext(); i++) {
+ if (clazz.isInstance(it.next()) == false) {
+ throw new IllegalArgumentException("The validated collection contains an element not of type "
+ + clazz.getName() + " at index: " + i);
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/EndlessLoopAdapterContainer.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/EndlessLoopAdapterContainer.java
similarity index 95%
rename from MAComponents/src/com/martinappl/components/ui/containers/EndlessLoopAdapterContainer.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/EndlessLoopAdapterContainer.java
index 58c931b..d9cedc6 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/EndlessLoopAdapterContainer.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/EndlessLoopAdapterContainer.java
@@ -1,1345 +1,1347 @@
-package com.martinappl.components.ui.containers;
-
-
-import java.lang.ref.WeakReference;
-import java.util.LinkedList;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewDebug.CapturedViewProperty;
-import android.widget.Adapter;
-import android.widget.AdapterView;
-import android.widget.Scroller;
-
-import com.martinappl.components.R;
-import com.martinappl.components.general.ToolBox;
-import com.martinappl.components.general.Validate;
-import com.martinappl.components.ui.containers.interfaces.IViewObserver;
-
-/**
- *
- * @author Martin Appl
- *
- * Endless loop with items filling from adapter. Currently only horizontal orientation is implemented
- * View recycling in adapter is supported. You are encouraged to recycle view in adapter if possible
- *
- */
-public class EndlessLoopAdapterContainer extends AdapterView {
- /** Children added with this layout mode will be added after the last child */
- protected static final int LAYOUT_MODE_AFTER = 0;
-
- /** Children added with this layout mode will be added before the first child */
- protected static final int LAYOUT_MODE_TO_BEFORE = 1;
-
- protected static final int SCROLLING_DURATION = 500;
-
-
-
- /** The adapter providing data for container */
- protected Adapter mAdapter;
-
- /** The adaptor position of the first visible item */
- protected int mFirstItemPosition;
-
- /** The adaptor position of the last visible item */
- protected int mLastItemPosition;
-
- /** The adaptor position of selected item */
- protected int mSelectedPosition = INVALID_POSITION;
-
- /** Left of current most left child*/
- protected int mLeftChildEdge;
-
- /** User is not touching the list */
- protected static final int TOUCH_STATE_RESTING = 1;
-
- /** User is scrolling the list */
- protected static final int TOUCH_STATE_SCROLLING = 2;
-
- /** Fling gesture in progress */
- protected static final int TOUCH_STATE_FLING = 3;
-
- /** Aligning in progress */
- protected static final int TOUCH_STATE_ALIGN = 4;
-
- protected static final int TOUCH_STATE_DISTANCE_SCROLL = 5;
-
- /** A list of cached (re-usable) item views */
- protected final LinkedList> mCachedItemViews = new LinkedList>();
-
- /** If there is not enough items to fill adapter, this value is set to true and scrolling is disabled. Since all items from adapter are on screen*/
- protected boolean isSrollingDisabled = false;
-
- /** Whether content should be repeated when there is not enough items to fill container */
- protected boolean shouldRepeat = true;
-
- /** Position to scroll adapter only if is in endless mode. This is done after layout if we find out we are endless, we must relayout*/
- protected int mScrollPositionIfEndless = -1;
-
- private IViewObserver mViewObserver;
-
-
- protected int mTouchState = TOUCH_STATE_RESTING;
-
- protected final Scroller mScroller = new Scroller(getContext());
- private VelocityTracker mVelocityTracker;
- private boolean mDataChanged;
-
- private int mTouchSlop;
- private int mMinimumVelocity;
- private int mMaximumVelocity;
-
- private boolean mAllowLongPress;
- private float mLastMotionX;
- private float mLastMotionY;
-// private long mDownTime;
-
- private final Point mDown = new Point();
- private boolean mHandleSelectionOnActionUp = false;
- private boolean mInterceptTouchEvents;
-// private boolean mCancelInIntercept;
-
- protected OnItemClickListener mOnItemClickListener;
- protected OnItemSelectedListener mOnItemSelectedListener;
-
- public EndlessLoopAdapterContainer(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
-
- final ViewConfiguration configuration = ViewConfiguration.get(context);
- mTouchSlop = configuration.getScaledTouchSlop();
- mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
- mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
-
- //init params from xml
- if(attrs != null){
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.EndlessLoopAdapterContainer, defStyle, 0);
-
- shouldRepeat = a.getBoolean(R.styleable.EndlessLoopAdapterContainer_shouldRepeat, true);
-
- a.recycle();
- }
- }
-
- public EndlessLoopAdapterContainer(Context context, AttributeSet attrs) {
- this(context, attrs,0);
-
- }
-
- public EndlessLoopAdapterContainer(Context context) {
- this(context,null);
- }
-
- private final DataSetObserver fDataObserver = new DataSetObserver() {
-
- @Override
- public void onChanged() {
- synchronized(this){
- mDataChanged = true;
- }
- invalidate();
- }
-
- @Override
- public void onInvalidated() {
- mAdapter = null;
- }
- };
-
-
- /**
- * Params describing position of child view in container
- * in HORIZONTAL mode TOP,CENTER,BOTTOM are active in VERTICAL mode LEFT,CENTER,RIGHT are active
- * @author Martin Appl
- *
- */
- public static class LoopLayoutParams extends MarginLayoutParams{
- public static final int TOP = 0;
- public static final int CENTER = 1;
- public static final int BOTTOM = 2;
- public static final int LEFT = 3;
- public static final int RIGHT = 4;
-
- public int position;
-// public int actualWidth;
-// public int actualHeight;
-
- public LoopLayoutParams(int w, int h) {
- super(w, h);
- position = CENTER;
- }
-
- public LoopLayoutParams(int w, int h,int pos){
- super(w, h);
- position = pos;
- }
-
- public LoopLayoutParams(android.view.ViewGroup.LayoutParams lp) {
- super(lp);
-
- if(lp!=null && lp instanceof MarginLayoutParams){
- MarginLayoutParams mp = (MarginLayoutParams) lp;
- leftMargin = mp.leftMargin;
- rightMargin = mp.rightMargin;
- topMargin = mp.topMargin;
- bottomMargin = mp.bottomMargin;
- }
-
- position = CENTER;
- }
-
-
- }
-
- protected LoopLayoutParams createLayoutParams(int w, int h){
- return new LoopLayoutParams(w, h);
- }
-
- protected LoopLayoutParams createLayoutParams(int w, int h,int pos){
- return new LoopLayoutParams(w, h, pos);
- }
-
- protected LoopLayoutParams createLayoutParams(android.view.ViewGroup.LayoutParams lp){
- return new LoopLayoutParams(lp);
- }
-
-
- public boolean isRepeatable() {
- return shouldRepeat;
- }
-
- public boolean isEndlessRightNow(){
- return !isSrollingDisabled;
- }
-
- public void setShouldRepeat(boolean shouldRepeat) {
- this.shouldRepeat = shouldRepeat;
- }
-
- /**
- * Sets position in adapter of first shown item in container
- * @param position
- */
- public void scrollToPosition(int position){
- if(position < 0 || position >= mAdapter.getCount()) throw new IndexOutOfBoundsException("Position must be in bounds of adapter values count");
-
- reset();
- refillInternal(position-1, position);
- invalidate();
- }
-
- public void scrollToPositionIfEndless(int position){
- if(position < 0 || position >= mAdapter.getCount()) throw new IndexOutOfBoundsException("Position must be in bounds of adapter values count");
-
- if(isEndlessRightNow() && getChildCount() != 0){
- scrollToPosition(position);
- }
- else{
- mScrollPositionIfEndless = position;
- }
- }
-
- /**
- * Returns position to which will container scroll on next relayout
- * @return scroll position on next layout or -1 if it will scroll nowhere
- */
- public int getScrollPositionIfEndless(){
- return mScrollPositionIfEndless;
- }
-
- /**
- * Get index of currently first item in adapter
- * @return
- */
- public int getScrollPosition(){
- return mFirstItemPosition;
- }
-
- /**
- * Return offset by which is edge off first item moved off screen.
- * You can persist it and insert to setFirstItemOffset() to restore exact scroll position
- *
- * @return offset of first item, or 0 if there is not enough items to fill container and scrolling is disabled
- */
- public int getFirstItemOffset(){
- if(isSrollingDisabled) return 0;
- else return getScrollX() - mLeftChildEdge;
- }
-
- /**
- * Negative number. Offset by which is left edge of first item moved off screen.
- * @param offset
- */
- public void setFirstItemOffset(int offset){
- scrollTo(offset, 0);
- }
-
- @Override
- public Adapter getAdapter() {
- return mAdapter;
- }
-
- @Override
- public void setAdapter(Adapter adapter) {
- if(mAdapter != null) {
- mAdapter.unregisterDataSetObserver(fDataObserver);
- }
- mAdapter = adapter;
- mAdapter.registerDataSetObserver(fDataObserver);
-
- if(adapter instanceof IViewObserver){
- setViewObserver((IViewObserver) adapter);
- }
-
- reset();
- refill();
- invalidate();
- }
-
- @Override
- public View getSelectedView() {
- if(mSelectedPosition == INVALID_POSITION) return null;
-
- final int index;
- if(mFirstItemPosition > mSelectedPosition){
- index = mSelectedPosition + mAdapter.getCount() - mFirstItemPosition;
- }
- else{
- index = mSelectedPosition - mFirstItemPosition;
- }
- if(index < 0 || index >= getChildCount()) return null;
-
- return getChildAt(index);
- }
-
-
- /**
- * Position index must be in range of adapter values (0 - getCount()-1) or -1 to unselect
- */
- @Override
- public void setSelection(int position) {
- if(mAdapter == null) throw new IllegalStateException("You are trying to set selection on widget without adapter");
- if(mAdapter.getCount() == 0 && position == 0) position = -1;
- if(position < -1 || position > mAdapter.getCount()-1)
- throw new IllegalArgumentException("Position index must be in range of adapter values (0 - getCount()-1) or -1 to unselect");
-
- View v = getSelectedView();
- if(v != null) v.setSelected(false);
-
-
- final int oldPos = mSelectedPosition;
- mSelectedPosition = position;
-
- if(position == -1){
- if(mOnItemSelectedListener != null) mOnItemSelectedListener.onNothingSelected(this);
- return;
- }
-
- v = getSelectedView();
- if(v != null) v.setSelected(true);
-
- if(oldPos != mSelectedPosition && mOnItemSelectedListener != null) mOnItemSelectedListener.onItemSelected(this, v, mSelectedPosition, getSelectedItemId());
- }
-
-
- private void reset() {
- scrollTo(0, 0);
- removeAllViewsInLayout();
- mFirstItemPosition = 0;
- mLastItemPosition = -1;
- mLeftChildEdge = 0;
- }
-
-
- @Override
- public void computeScroll() {
- // if we don't have an adapter, we don't need to do anything
- if (mAdapter == null) {
- return;
- }
- if(mAdapter.getCount() == 0){
- return;
- }
-
- if (mScroller.computeScrollOffset()) {
- if(mScroller.getFinalX() == mScroller.getCurrX()){
- mScroller.abortAnimation();
- mTouchState = TOUCH_STATE_RESTING;
- if(!checkScrollPosition())
- clearChildrenCache();
- return;
- }
-
- int x = mScroller.getCurrX();
- scrollTo(x, 0);
-
- postInvalidate();
- }
- else if(mTouchState == TOUCH_STATE_FLING || mTouchState == TOUCH_STATE_DISTANCE_SCROLL){
- mTouchState = TOUCH_STATE_RESTING;
- if(!checkScrollPosition())
- clearChildrenCache();
- }
-
- if(mDataChanged){
- removeAllViewsInLayout();
- refillOnChange(mFirstItemPosition);
- return;
- }
-
- relayout();
- removeNonVisibleViews();
- refillRight();
- refillLeft();
-
- }
-
- /**
- *
- * @param velocityY The initial velocity in the Y direction. Positive
- * numbers mean that the finger/cursor is moving down the screen,
- * which means we want to scroll towards the top.
- * @param velocityX The initial velocity in the X direction. Positive
- * numbers mean that the finger/cursor is moving right the screen,
- * which means we want to scroll towards the top.
- */
- public void fling(int velocityX, int velocityY){
- mTouchState = TOUCH_STATE_FLING;
- final int x = getScrollX();
- final int y = getScrollY();
-
- mScroller.fling(x, y, velocityX, velocityY, Integer.MIN_VALUE,Integer.MAX_VALUE, Integer.MIN_VALUE,Integer.MAX_VALUE);
-
- invalidate();
- }
-
- /**
- * Scroll widget by given distance in pixels
- * @param dx
- */
- public void scroll(int dx){
- mScroller.startScroll(getScrollX(), 0, dx, 0, SCROLLING_DURATION);
- mTouchState = TOUCH_STATE_DISTANCE_SCROLL;
- invalidate();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right,
- int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- // if we don't have an adapter, we don't need to do anything
- if (mAdapter == null) {
- return;
- }
-
- refillInternal(mLastItemPosition,mFirstItemPosition);
- }
-
- /**
- * Method for actualizing content after data change in adapter. It is expected container was emptied before
- * @param firstItemPosition
- */
- protected void refillOnChange(int firstItemPosition){
- refillInternal(firstItemPosition-1, firstItemPosition);
- }
-
-
- protected void refillInternal(final int lastItemPos,final int firstItemPos){
- // if we don't have an adapter, we don't need to do anything
- if (mAdapter == null) {
- return;
- }
- if(mAdapter.getCount() == 0){
- return;
- }
-
- if(getChildCount() == 0){
- fillFirstTime(lastItemPos, firstItemPos);
- }
- else{
- relayout();
- removeNonVisibleViews();
- refillRight();
- refillLeft();
- }
- }
-
- /**
- * Check if container visible area is filled and refill empty areas
- */
- private void refill(){
- scrollTo(0, 0);
- refillInternal(-1, 0);
- }
-
-// protected void measureChild(View child, LoopLayoutParams params){
-// //prepare spec for measurement
-// final int specW, specH;
-//
-// specW = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.UNSPECIFIED), 0, params.width);
-// specH = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.UNSPECIFIED), 0, params.height);
-//
-////// final boolean useMeasuredW, useMeasuredH;
-//// if(params.height >= 0){
-//// specH = MeasureSpec.EXACTLY | params.height;
-////// useMeasuredH = false;
-//// }
-//// else{
-//// if(params.height == LayoutParams.MATCH_PARENT){
-//// specH = MeasureSpec.EXACTLY | getHeight();
-//// params.height = getHeight();
-////// useMeasuredH = false;
-//// }else{
-//// specH = MeasureSpec.AT_MOST | getHeight();
-////// useMeasuredH = true;
-//// }
-//// }
-////
-//// if(params.width >= 0){
-//// specW = MeasureSpec.EXACTLY | params.width;
-////// useMeasuredW = false;
-//// }
-//// else{
-//// if(params.width == LayoutParams.MATCH_PARENT){
-//// specW = MeasureSpec.EXACTLY | getWidth();
-//// params.width = getWidth();
-////// useMeasuredW = false;
-//// }else{
-//// specW = MeasureSpec.UNSPECIFIED;
-////// useMeasuredW = true;
-//// }
-//// }
-//
-// //measure
-// child.measure(specW, specH);
-// //put measured values into layout params from where they will be used in layout.
-// //Use measured values only if exact values was not specified in layout params.
-//// if(useMeasuredH) params.actualHeight = child.getMeasuredHeight();
-//// else params.actualHeight = params.height;
-////
-//// if(useMeasuredW) params.actualWidth = child.getMeasuredWidth();
-//// else params.actualWidth = params.width;
-// }
-
- protected void measureChild(View child){
- final int pwms = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
- final int phms = MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY);
- measureChild(child, pwms, phms);
- }
-
- private void relayout(){
- final int c = getChildCount();
- int left = mLeftChildEdge;
-
- View child;
- LoopLayoutParams lp;
- for(int i = 0; i < c; i++){
- child = getChildAt(i);
- lp = (LoopLayoutParams) child.getLayoutParams();
- measureChild(child);
-
- left = layoutChildHorizontal(child, left, lp);
- }
-
- }
-
-
- protected void fillFirstTime(final int lastItemPos,final int firstItemPos){
- final int leftScreenEdge = 0;
- final int rightScreenEdge = leftScreenEdge + getWidth();
-
- int right;
- int left;
- View child;
-
- boolean isRepeatingNow = false;
-
- //scrolling is enabled until we find out we don't have enough items
- isSrollingDisabled = false;
-
- mLastItemPosition = lastItemPos;
- mFirstItemPosition = firstItemPos;
- mLeftChildEdge = 0;
- right = mLeftChildEdge;
- left = mLeftChildEdge;
-
- while(right < rightScreenEdge){
- mLastItemPosition++;
-
- if(isRepeatingNow && mLastItemPosition >= firstItemPos) return;
-
- if(mLastItemPosition >= mAdapter.getCount()){
- if(firstItemPos == 0 && shouldRepeat) mLastItemPosition = 0;
- else{
- if(firstItemPos > 0){
- mLastItemPosition = 0;
- isRepeatingNow = true;
- }
- else if(!shouldRepeat){
- mLastItemPosition--;
- isSrollingDisabled = true;
- final int w = right-mLeftChildEdge;
- final int dx = (getWidth() - w)/2;
- scrollTo(-dx, 0);
- return;
- }
-
- }
- }
-
- if(mLastItemPosition >= mAdapter.getCount() ){
- Log.wtf("EndlessLoop", "mLastItemPosition > mAdapter.getCount()");
- return;
- }
-
- child = mAdapter.getView(mLastItemPosition, getCachedView(), this);
- Validate.notNull(child,"Your adapter has returned null from getView.");
- child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_AFTER);
- left = layoutChildHorizontal(child, left, (LoopLayoutParams) child.getLayoutParams());
- right = child.getRight();
-
- //if selected view is going to screen, set selected state on him
- if(mLastItemPosition == mSelectedPosition){
- child.setSelected(true);
- }
-
- }
-
- if(mScrollPositionIfEndless > 0){
- final int p = mScrollPositionIfEndless;
- mScrollPositionIfEndless = -1;
- removeAllViewsInLayout();
- refillOnChange(p);
- }
- }
-
-
- /**
- * Checks and refills empty area on the right
- */
- protected void refillRight(){
- if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override override first init to scrolling disabled by falling to this branch
- if(getChildCount() == 0) return;
-
- final int leftScreenEdge = getScrollX();
- final int rightScreenEdge = leftScreenEdge + getWidth();
-
- View child = getChildAt(getChildCount() - 1);
- int right = child.getRight();
- int currLayoutLeft = right + ((LoopLayoutParams)child.getLayoutParams()).rightMargin;
- while(right < rightScreenEdge){
- mLastItemPosition++;
- if(mLastItemPosition >= mAdapter.getCount()) mLastItemPosition = 0;
-
- child = mAdapter.getView(mLastItemPosition, getCachedView(), this);
- Validate.notNull(child,"Your adapter has returned null from getView.");
- child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_AFTER);
- currLayoutLeft = layoutChildHorizontal(child, currLayoutLeft, (LoopLayoutParams) child.getLayoutParams());
- right = child.getRight();
-
- //if selected view is going to screen, set selected state on him
- if(mLastItemPosition == mSelectedPosition){
- child.setSelected(true);
- }
- }
- }
-
- /**
- * Checks and refills empty area on the left
- */
- protected void refillLeft(){
- if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override first init to scrolling disabled by falling to this branch
- if(getChildCount() == 0) return;
-
- final int leftScreenEdge = getScrollX();
-
- View child = getChildAt(0);
- int childLeft = child.getLeft();
- int currLayoutRight = childLeft - ((LoopLayoutParams)child.getLayoutParams()).leftMargin;
- while(currLayoutRight > leftScreenEdge){
- mFirstItemPosition--;
- if(mFirstItemPosition < 0) mFirstItemPosition = mAdapter.getCount()-1;
-
- child = mAdapter.getView(mFirstItemPosition, getCachedView(), this);
- Validate.notNull(child,"Your adapter has returned null from getView.");
- child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_TO_BEFORE);
- currLayoutRight = layoutChildHorizontalToBefore(child, currLayoutRight, (LoopLayoutParams) child.getLayoutParams());
- childLeft = child.getLeft() - ((LoopLayoutParams)child.getLayoutParams()).leftMargin;
- //update left edge of children in container
- mLeftChildEdge = childLeft;
-
- //if selected view is going to screen, set selected state on him
- if(mFirstItemPosition == mSelectedPosition){
- child.setSelected(true);
- }
- }
- }
-
-// /**
-// * Checks and refills empty area on the left
-// */
-// protected void refillLeft(){
-// if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override override first init to scrolling disabled by falling to this branch
-// final int leftScreenEdge = getScrollX();
-//
-// View child = getChildAt(0);
-// int currLayoutRight = child.getRight();
-// while(currLayoutRight > leftScreenEdge){
-// mFirstItemPosition--;
-// if(mFirstItemPosition < 0) mFirstItemPosition = mAdapter.getCount()-1;
-//
-// child = mAdapter.getView(mFirstItemPosition, getCachedView(mFirstItemPosition), this);
-// child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_TO_BEFORE);
-// currLayoutRight = layoutChildHorizontalToBefore(child, currLayoutRight, (LoopLayoutParams) child.getLayoutParams());
-//
-// //update left edge of children in container
-// mLeftChildEdge = child.getLeft();
-//
-// //if selected view is going to screen, set selected state on him
-// if(mFirstItemPosition == mSelectedPosition){
-// child.setSelected(true);
-// }
-// }
-// }
-
- /**
- * Removes view that are outside of the visible part of the list. Will not
- * remove all views.
- */
- protected void removeNonVisibleViews() {
- if(getChildCount() == 0) return;
-
- final int leftScreenEdge = getScrollX();
- final int rightScreenEdge = leftScreenEdge + getWidth();
-
- // check if we should remove any views in the left
- View firstChild = getChildAt(0);
- final int leftedge = firstChild.getLeft() - ((LoopLayoutParams)firstChild.getLayoutParams()).leftMargin;
- if(leftedge != mLeftChildEdge) throw new IllegalStateException("firstChild.getLeft() != mLeftChildEdge");
- while (firstChild != null && firstChild.getRight() + ((LoopLayoutParams)firstChild.getLayoutParams()).rightMargin < leftScreenEdge) {
- //if selected view is going off screen, remove selected state
- firstChild.setSelected(false);
-
- // remove view
- removeViewInLayout(firstChild);
-
- if(mViewObserver != null) mViewObserver.onViewRemovedFromParent(firstChild, mFirstItemPosition);
- WeakReference ref = new WeakReference(firstChild);
- mCachedItemViews.addLast(ref);
-
- mFirstItemPosition++;
- if(mFirstItemPosition >= mAdapter.getCount()) mFirstItemPosition = 0;
-
- // update left item position
- mLeftChildEdge = getChildAt(0).getLeft() - ((LoopLayoutParams)getChildAt(0).getLayoutParams()).leftMargin;
-
- // Continue to check the next child only if we have more than
- // one child left
- if (getChildCount() > 1) {
- firstChild = getChildAt(0);
- } else {
- firstChild = null;
- }
- }
-
- // check if we should remove any views in the right
- View lastChild = getChildAt(getChildCount() - 1);
- while (lastChild != null && firstChild!=null && lastChild.getLeft() - ((LoopLayoutParams)firstChild.getLayoutParams()).leftMargin > rightScreenEdge) {
- //if selected view is going off screen, remove selected state
- lastChild.setSelected(false);
-
- // remove the right view
- removeViewInLayout(lastChild);
-
- if(mViewObserver != null) mViewObserver.onViewRemovedFromParent(lastChild, mLastItemPosition);
- WeakReference ref = new WeakReference(lastChild);
- mCachedItemViews.addLast(ref);
-
- mLastItemPosition--;
- if(mLastItemPosition < 0) mLastItemPosition = mAdapter.getCount()-1;
-
- // Continue to check the next child only if we have more than
- // one child left
- if (getChildCount() > 1) {
- lastChild = getChildAt(getChildCount() - 1);
- } else {
- lastChild = null;
- }
- }
- }
-
-
- /**
- * Adds a view as a child view and takes care of measuring it
- *
- * @param child The view to add
- * @param layoutMode Either LAYOUT_MODE_LEFT or LAYOUT_MODE_RIGHT
- * @return child which was actually added to container, subclasses can override to introduce frame views
- */
- protected View addAndMeasureChildHorizontal(final View child, final int layoutMode) {
- LayoutParams lp = child.getLayoutParams();
- LoopLayoutParams params;
- if (lp == null) {
- params = createLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- }
- else{
- if(lp!=null && lp instanceof LoopLayoutParams) params = (LoopLayoutParams) lp;
- else params = createLayoutParams(lp);
- }
- final int index = layoutMode == LAYOUT_MODE_TO_BEFORE ? 0 : -1;
- addViewInLayout(child, index, params, true);
-
- measureChild(child);
- child.setDrawingCacheEnabled(true);
-
- return child;
- }
-
-
-
- /**
- * Layouts children from left to right
- * @param left positon for left edge in parent container
- * @param lp layout params
- * @return new left
- */
- protected int layoutChildHorizontal(View v,int left, LoopLayoutParams lp){
- int l,t,r,b;
-
- switch(lp.position){
- case LoopLayoutParams.TOP:
- l = left + lp.leftMargin;
- t = lp.topMargin;
- r = l + v.getMeasuredWidth();
- b = t + v.getMeasuredHeight();
- break;
- case LoopLayoutParams.BOTTOM:
- b = getHeight() - lp.bottomMargin;
- t = b - v.getMeasuredHeight();
- l = left + lp.leftMargin;
- r = l + v.getMeasuredWidth();
- break;
- case LoopLayoutParams.CENTER:
- l = left + lp.leftMargin;
- r = l + v.getMeasuredWidth();
- final int x = (getHeight() - v.getMeasuredHeight())/2;
- t = x;
- b = t + v.getMeasuredHeight();
- break;
- default:
- throw new RuntimeException("Only TOP,BOTTOM,CENTER are alowed in horizontal orientation");
- }
-
-
- v.layout(l, t, r, b);
- return r + lp.rightMargin;
- }
-
- /**
- * Layout children from right to left
- */
- protected int layoutChildHorizontalToBefore(View v,int right , LoopLayoutParams lp){
- final int left = right - v.getMeasuredWidth() - lp.leftMargin - lp.rightMargin;
- layoutChildHorizontal(v, left, lp);
- return left;
- }
-
- /**
- * Allows to make scroll alignments
- * @return true if invalidate() was issued, and container is going to scroll
- */
- protected boolean checkScrollPosition(){
- return false;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
-
- /*
- * This method JUST determines whether we want to intercept the motion.
- * If we return true, onTouchEvent will be called and we do the actual
- * scrolling there.
- */
-
-
- /*
- * Shortcut the most recurring case: the user is in the dragging
- * state and he is moving his finger. We want to intercept this
- * motion.
- */
- final int action = ev.getAction();
- if ((action == MotionEvent.ACTION_MOVE) && (mTouchState == TOUCH_STATE_SCROLLING)) {
- return true;
- }
-
- final float x = ev.getX();
- final float y = ev.getY();
- switch (action) {
- case MotionEvent.ACTION_MOVE:
- //if we have scrolling disabled, we don't do anything
- if(!shouldRepeat && isSrollingDisabled) return false;
-
- /*
- * not dragging, otherwise the shortcut would have caught it. Check
- * whether the user has moved far enough from his original down touch.
- */
-
- /*
- * Locally do absolute value. mLastMotionX is set to the x value
- * of the down event.
- */
- final int xDiff = (int) Math.abs(x - mLastMotionX);
- final int yDiff = (int) Math.abs(y - mLastMotionY);
-
- final int touchSlop = mTouchSlop;
- final boolean xMoved = xDiff > touchSlop;
- final boolean yMoved = yDiff > touchSlop;
-
- if (xMoved) {
-
- // Scroll if the user moved far enough along the X axis
- mTouchState = TOUCH_STATE_SCROLLING;
- mHandleSelectionOnActionUp = false;
- enableChildrenCache();
-
- // Either way, cancel any pending longpress
- if (mAllowLongPress) {
- mAllowLongPress = false;
- // Try canceling the long press. It could also have been scheduled
- // by a distant descendant, so use the mAllowLongPress flag to block
- // everything
- cancelLongPress();
- }
- }
- if(yMoved){
- mHandleSelectionOnActionUp = false;
- if (mAllowLongPress) {
- mAllowLongPress = false;
- cancelLongPress();
- }
- }
- break;
-
- case MotionEvent.ACTION_DOWN:
- // Remember location of down touch
- mLastMotionX = x;
- mLastMotionY = y;
- mAllowLongPress = true;
-// mCancelInIntercept = false;
-
- mDown.x = (int) x;
- mDown.y = (int) y;
-
- /*
- * If being flinged and user touches the screen, initiate drag;
- * otherwise don't. mScroller.isFinished should be false when
- * being flinged.
- */
- mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTING : TOUCH_STATE_SCROLLING;
- //if he had normal click in rested state, remember for action up check
- if(mTouchState == TOUCH_STATE_RESTING){
- mHandleSelectionOnActionUp = true;
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- mDown.x = -1;
- mDown.y = -1;
-// mCancelInIntercept = true;
- break;
- case MotionEvent.ACTION_UP:
- //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
- if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
- final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
- if((ev.getEventTime() - ev.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
- }
- // Release the drag
- mAllowLongPress = false;
- mHandleSelectionOnActionUp = false;
- mDown.x = -1;
- mDown.y = -1;
- if(mTouchState == TOUCH_STATE_SCROLLING){
- if(checkScrollPosition()){
- break;
- }
- }
- mTouchState = TOUCH_STATE_RESTING;
- clearChildrenCache();
- break;
- }
-
- mInterceptTouchEvents = mTouchState == TOUCH_STATE_SCROLLING;
- return mInterceptTouchEvents;
-
- }
-
-// /**
-// * Allow subclasses to override this to always intercept events
-// * @return
-// */
-// protected boolean interceptEvents(){
-// /*
-// * The only time we want to intercept motion events is if we are in the
-// * drag mode.
-// */
-// return mTouchState == TOUCH_STATE_SCROLLING;
-// }
-
- protected void handleClick(Point p){
- final int c = getChildCount();
- View v;
- final Rect r = new Rect();
- for(int i=0; i < c; i++){
- v = getChildAt(i);
- v.getHitRect(r);
- if(r.contains(getScrollX() + p.x, getScrollY() + p.y)){
- final View old = getSelectedView();
- if(old != null) old.setSelected(false);
-
- int position = mFirstItemPosition + i;
- if(position >= mAdapter.getCount()) position = position - mAdapter.getCount();
-
-
- mSelectedPosition = position;
- v.setSelected(true);
-
- if(mOnItemClickListener != null) mOnItemClickListener.onItemClick(this, v, position , getItemIdAtPosition(position));
- if(mOnItemSelectedListener != null) mOnItemSelectedListener.onItemSelected(this, v, position, getItemIdAtPosition(position));
-
- break;
- }
- }
- }
-
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // if we don't have an adapter, we don't need to do anything
- if (mAdapter == null) {
- return false;
- }
-
-
-
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
-
- final int action = event.getAction();
- final float x = event.getX();
- final float y = event.getY();
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- /*
- * If being flinged and user touches, stop the fling. isFinished
- * will be false if being flinged.
- */
- if (!mScroller.isFinished()) {
- mScroller.forceFinished(true);
- }
-
- // Remember where the motion event started
- mLastMotionX = x;
- mLastMotionY = y;
-
- break;
- case MotionEvent.ACTION_MOVE:
- //if we have scrolling disabled, we don't do anything
- if(!shouldRepeat && isSrollingDisabled) return false;
-
- if (mTouchState == TOUCH_STATE_SCROLLING) {
- // Scroll to follow the motion event
- final int deltaX = (int) (mLastMotionX - x);
- mLastMotionX = x;
- mLastMotionY = y;
-
- int sx = getScrollX() + deltaX;
-
- scrollTo(sx, 0);
-
- }
- else{
- final int xDiff = (int) Math.abs(x - mLastMotionX);
-
- final int touchSlop = mTouchSlop;
- final boolean xMoved = xDiff > touchSlop;
-
-
- if (xMoved) {
-
- // Scroll if the user moved far enough along the X axis
- mTouchState = TOUCH_STATE_SCROLLING;
- enableChildrenCache();
-
- // Either way, cancel any pending longpress
- if (mAllowLongPress) {
- mAllowLongPress = false;
- // Try canceling the long press. It could also have been scheduled
- // by a distant descendant, so use the mAllowLongPress flag to block
- // everything
- cancelLongPress();
- }
- }
- }
- break;
- case MotionEvent.ACTION_UP:
-
- //this must be here, in case no child view returns true,
- //events will propagate back here and on intercept touch event wont be called again
- //in case of no parent it propagates here, in case of parent it usualy propagates to on cancel
- if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
- final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
- if((event.getEventTime() - event.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
- mHandleSelectionOnActionUp = false;
- }
-
- //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
- if (mTouchState == TOUCH_STATE_SCROLLING) {
-
- mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialXVelocity = (int) mVelocityTracker.getXVelocity();
- int initialYVelocity = (int) mVelocityTracker.getYVelocity();
-
- if (Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) {
- fling(-initialXVelocity, -initialYVelocity);
- }
- else{
- // Release the drag
- clearChildrenCache();
- mTouchState = TOUCH_STATE_RESTING;
- checkScrollPosition();
- mAllowLongPress = false;
-
- mDown.x = -1;
- mDown.y = -1;
- }
-
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
-
- break;
- }
-
- // Release the drag
- clearChildrenCache();
- mTouchState = TOUCH_STATE_RESTING;
- mAllowLongPress = false;
-
- mDown.x = -1;
- mDown.y = -1;
-
- break;
- case MotionEvent.ACTION_CANCEL:
-
- //this must be here, in case no child view returns true,
- //events will propagate back here and on intercept touch event wont be called again
- //instead we get cancel here, since we stated we shouldn't intercept events and propagate them to children
- //but events propagated back here, because no child was interested
-// if(!mInterceptTouchEvents && mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
-// handleClick(mDown);
-// mHandleSelectionOnActionUp = false;
-// }
-
- mAllowLongPress = false;
-
- mDown.x = -1;
- mDown.y = -1;
-
- if(mTouchState == TOUCH_STATE_SCROLLING){
- if(checkScrollPosition()){
- break;
- }
- }
-
- mTouchState = TOUCH_STATE_RESTING;
- }
-
- return true;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- checkScrollFocusLeft();
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- checkScrollFocusRight();
- break;
- default:
- break;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- /**
- * Moves with scroll window if focus hits one view before end of screen
- */
- private void checkScrollFocusLeft(){
- final View focused = getFocusedChild();
- if(getChildCount() >= 2 ){
- View second = getChildAt(1);
- View first = getChildAt(0);
-
- if(focused == second){
- scroll(-first.getWidth());
- }
- }
- }
-
- private void checkScrollFocusRight(){
- final View focused = getFocusedChild();
- if(getChildCount() >= 2 ){
- View last = getChildAt(getChildCount()-1);
- View lastButOne = getChildAt(getChildCount()-2);
-
- if(focused == lastButOne){
- scroll(last.getWidth());
- }
- }
- }
-
- /**
- * Check if list of weak references has any view still in memory to offer for recyclation
- * @return cached view
- */
- protected View getCachedView(){
- if (mCachedItemViews.size() != 0) {
- View v;
- do{
- v = mCachedItemViews.removeFirst().get();
- }
- while(v == null && mCachedItemViews.size() != 0);
- return v;
- }
- return null;
- }
-
- protected void enableChildrenCache() {
- setChildrenDrawnWithCacheEnabled(true);
- setChildrenDrawingCacheEnabled(true);
- }
-
- protected void clearChildrenCache() {
- setChildrenDrawnWithCacheEnabled(false);
- }
-
- @Override
- public void setOnItemClickListener(
- android.widget.AdapterView.OnItemClickListener listener) {
- mOnItemClickListener = listener;
- }
-
- @Override
- public void setOnItemSelectedListener(
- android.widget.AdapterView.OnItemSelectedListener listener) {
- mOnItemSelectedListener = listener;
- }
-
- @Override
- @CapturedViewProperty
- public int getSelectedItemPosition() {
- return mSelectedPosition;
- }
-
- /**
- * Only set value for selection position field, no gui updates are done
- * for setting selection with gui updates and callback calls use setSelection
- * @param position
- */
- public void setSeletedItemPosition(int position){
- if(mAdapter.getCount() == 0 && position == 0) position = -1;
- if(position < -1 || position > mAdapter.getCount()-1)
- throw new IllegalArgumentException("Position index must be in range of adapter values (0 - getCount()-1) or -1 to unselect");
-
- mSelectedPosition = position;
- }
-
- @Override
- @CapturedViewProperty
- public long getSelectedItemId() {
- return mAdapter.getItemId(mSelectedPosition);
- }
-
- @Override
- public Object getSelectedItem() {
- return getSelectedView();
- }
-
- @Override
- @CapturedViewProperty
- public int getCount() {
- if(mAdapter != null) return mAdapter.getCount();
- else return 0;
- }
-
- @Override
- public int getPositionForView(View view) {
- final int c = getChildCount();
- View v;
- for(int i = 0; i < c; i++){
- v = getChildAt(i);
- if(v == view) return mFirstItemPosition + i;
- }
- return INVALID_POSITION;
- }
-
- @Override
- public int getFirstVisiblePosition() {
- return mFirstItemPosition;
- }
-
- @Override
- public int getLastVisiblePosition() {
- return mLastItemPosition;
- }
-
- @Override
- public Object getItemAtPosition(int position) {
- final int index;
- if(mFirstItemPosition > position){
- index = position + mAdapter.getCount() - mFirstItemPosition;
- }
- else{
- index = position - mFirstItemPosition;
- }
- if(index < 0 || index >= getChildCount()) return null;
-
- return getChildAt(index);
- }
-
- @Override
- public long getItemIdAtPosition(int position) {
- return mAdapter.getItemId(position);
- }
-
- @Override
- public boolean performItemClick(View view, int position, long id) {
- throw new UnsupportedOperationException();
- }
-
-
- public void setViewObserver(IViewObserver viewObserver) {
- this.mViewObserver = viewObserver;
- }
-
-
-}
-
-
+package it.moondroid.coverflow.components.ui.containers;
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewDebug.CapturedViewProperty;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.Scroller;
+
+import java.lang.ref.WeakReference;
+import java.util.LinkedList;
+
+import it.moondroid.coverflow.R;
+import it.moondroid.coverflow.components.general.ToolBox;
+import it.moondroid.coverflow.components.general.Validate;
+import it.moondroid.coverflow.components.ui.containers.interfaces.IViewObserver;
+
+
+/**
+ *
+ * @author Martin Appl
+ *
+ * Endless loop with items filling from adapter. Currently only horizontal orientation is implemented
+ * View recycling in adapter is supported. You are encouraged to recycle view in adapter if possible
+ *
+ */
+public class EndlessLoopAdapterContainer extends AdapterView {
+ /** Children added with this layout mode will be added after the last child */
+ protected static final int LAYOUT_MODE_AFTER = 0;
+
+ /** Children added with this layout mode will be added before the first child */
+ protected static final int LAYOUT_MODE_TO_BEFORE = 1;
+
+ protected static final int SCROLLING_DURATION = 500;
+
+
+
+ /** The adapter providing data for container */
+ protected Adapter mAdapter;
+
+ /** The adaptor position of the first visible item */
+ protected int mFirstItemPosition;
+
+ /** The adaptor position of the last visible item */
+ protected int mLastItemPosition;
+
+ /** The adaptor position of selected item */
+ protected int mSelectedPosition = INVALID_POSITION;
+
+ /** Left of current most left child*/
+ protected int mLeftChildEdge;
+
+ /** User is not touching the list */
+ protected static final int TOUCH_STATE_RESTING = 1;
+
+ /** User is scrolling the list */
+ protected static final int TOUCH_STATE_SCROLLING = 2;
+
+ /** Fling gesture in progress */
+ protected static final int TOUCH_STATE_FLING = 3;
+
+ /** Aligning in progress */
+ protected static final int TOUCH_STATE_ALIGN = 4;
+
+ protected static final int TOUCH_STATE_DISTANCE_SCROLL = 5;
+
+ /** A list of cached (re-usable) item views */
+ protected final LinkedList> mCachedItemViews = new LinkedList>();
+
+ /** If there is not enough items to fill adapter, this value is set to true and scrolling is disabled. Since all items from adapter are on screen*/
+ protected boolean isSrollingDisabled = false;
+
+ /** Whether content should be repeated when there is not enough items to fill container */
+ protected boolean shouldRepeat = true;
+
+ /** Position to scroll adapter only if is in endless mode. This is done after layout if we find out we are endless, we must relayout*/
+ protected int mScrollPositionIfEndless = -1;
+
+ private IViewObserver mViewObserver;
+
+
+ protected int mTouchState = TOUCH_STATE_RESTING;
+
+ protected final Scroller mScroller = new Scroller(getContext());
+ private VelocityTracker mVelocityTracker;
+ private boolean mDataChanged;
+
+ private int mTouchSlop;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
+
+ private boolean mAllowLongPress;
+ private float mLastMotionX;
+ private float mLastMotionY;
+// private long mDownTime;
+
+ private final Point mDown = new Point();
+ private boolean mHandleSelectionOnActionUp = false;
+ private boolean mInterceptTouchEvents;
+// private boolean mCancelInIntercept;
+
+ protected OnItemClickListener mOnItemClickListener;
+ protected OnItemSelectedListener mOnItemSelectedListener;
+
+ public EndlessLoopAdapterContainer(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+
+ //init params from xml
+ if(attrs != null){
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.EndlessLoopAdapterContainer, defStyle, 0);
+
+ shouldRepeat = a.getBoolean(R.styleable.EndlessLoopAdapterContainer_shouldRepeat, true);
+
+ a.recycle();
+ }
+ }
+
+ public EndlessLoopAdapterContainer(Context context, AttributeSet attrs) {
+ this(context, attrs,0);
+
+ }
+
+ public EndlessLoopAdapterContainer(Context context) {
+ this(context,null);
+ }
+
+ private final DataSetObserver fDataObserver = new DataSetObserver() {
+
+ @Override
+ public void onChanged() {
+ synchronized(this){
+ mDataChanged = true;
+ }
+ invalidate();
+ }
+
+ @Override
+ public void onInvalidated() {
+ mAdapter = null;
+ }
+ };
+
+
+ /**
+ * Params describing position of child view in container
+ * in HORIZONTAL mode TOP,CENTER,BOTTOM are active in VERTICAL mode LEFT,CENTER,RIGHT are active
+ * @author Martin Appl
+ *
+ */
+ public static class LoopLayoutParams extends MarginLayoutParams{
+ public static final int TOP = 0;
+ public static final int CENTER = 1;
+ public static final int BOTTOM = 2;
+ public static final int LEFT = 3;
+ public static final int RIGHT = 4;
+
+ public int position;
+// public int actualWidth;
+// public int actualHeight;
+
+ public LoopLayoutParams(int w, int h) {
+ super(w, h);
+ position = CENTER;
+ }
+
+ public LoopLayoutParams(int w, int h,int pos){
+ super(w, h);
+ position = pos;
+ }
+
+ public LoopLayoutParams(LayoutParams lp) {
+ super(lp);
+
+ if(lp!=null && lp instanceof MarginLayoutParams){
+ MarginLayoutParams mp = (MarginLayoutParams) lp;
+ leftMargin = mp.leftMargin;
+ rightMargin = mp.rightMargin;
+ topMargin = mp.topMargin;
+ bottomMargin = mp.bottomMargin;
+ }
+
+ position = CENTER;
+ }
+
+
+ }
+
+ protected LoopLayoutParams createLayoutParams(int w, int h){
+ return new LoopLayoutParams(w, h);
+ }
+
+ protected LoopLayoutParams createLayoutParams(int w, int h,int pos){
+ return new LoopLayoutParams(w, h, pos);
+ }
+
+ protected LoopLayoutParams createLayoutParams(LayoutParams lp){
+ return new LoopLayoutParams(lp);
+ }
+
+
+ public boolean isRepeatable() {
+ return shouldRepeat;
+ }
+
+ public boolean isEndlessRightNow(){
+ return !isSrollingDisabled;
+ }
+
+ public void setShouldRepeat(boolean shouldRepeat) {
+ this.shouldRepeat = shouldRepeat;
+ }
+
+ /**
+ * Sets position in adapter of first shown item in container
+ * @param position
+ */
+ public void scrollToPosition(int position){
+ if(position < 0 || position >= mAdapter.getCount()) throw new IndexOutOfBoundsException("Position must be in bounds of adapter values count");
+
+ reset();
+ refillInternal(position-1, position);
+ invalidate();
+ }
+
+ public void scrollToPositionIfEndless(int position){
+ if(position < 0 || position >= mAdapter.getCount()) throw new IndexOutOfBoundsException("Position must be in bounds of adapter values count");
+
+ if(isEndlessRightNow() && getChildCount() != 0){
+ scrollToPosition(position);
+ }
+ else{
+ mScrollPositionIfEndless = position;
+ }
+ }
+
+ /**
+ * Returns position to which will container scroll on next relayout
+ * @return scroll position on next layout or -1 if it will scroll nowhere
+ */
+ public int getScrollPositionIfEndless(){
+ return mScrollPositionIfEndless;
+ }
+
+ /**
+ * Get index of currently first item in adapter
+ * @return
+ */
+ public int getScrollPosition(){
+ return mFirstItemPosition;
+ }
+
+ /**
+ * Return offset by which is edge off first item moved off screen.
+ * You can persist it and insert to setFirstItemOffset() to restore exact scroll position
+ *
+ * @return offset of first item, or 0 if there is not enough items to fill container and scrolling is disabled
+ */
+ public int getFirstItemOffset(){
+ if(isSrollingDisabled) return 0;
+ else return getScrollX() - mLeftChildEdge;
+ }
+
+ /**
+ * Negative number. Offset by which is left edge of first item moved off screen.
+ * @param offset
+ */
+ public void setFirstItemOffset(int offset){
+ scrollTo(offset, 0);
+ }
+
+ @Override
+ public Adapter getAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public void setAdapter(Adapter adapter) {
+ if(mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(fDataObserver);
+ }
+ mAdapter = adapter;
+ mAdapter.registerDataSetObserver(fDataObserver);
+
+ if(adapter instanceof IViewObserver){
+ setViewObserver((IViewObserver) adapter);
+ }
+
+ reset();
+ refill();
+ invalidate();
+ }
+
+ @Override
+ public View getSelectedView() {
+ if(mSelectedPosition == INVALID_POSITION) return null;
+
+ final int index;
+ if(mFirstItemPosition > mSelectedPosition){
+ index = mSelectedPosition + mAdapter.getCount() - mFirstItemPosition;
+ }
+ else{
+ index = mSelectedPosition - mFirstItemPosition;
+ }
+ if(index < 0 || index >= getChildCount()) return null;
+
+ return getChildAt(index);
+ }
+
+
+ /**
+ * Position index must be in range of adapter values (0 - getCount()-1) or -1 to unselect
+ */
+ @Override
+ public void setSelection(int position) {
+ if(mAdapter == null) throw new IllegalStateException("You are trying to set selection on widget without adapter");
+ if(mAdapter.getCount() == 0 && position == 0) position = -1;
+ if(position < -1 || position > mAdapter.getCount()-1)
+ throw new IllegalArgumentException("Position index must be in range of adapter values (0 - getCount()-1) or -1 to unselect");
+
+ View v = getSelectedView();
+ if(v != null) v.setSelected(false);
+
+
+ final int oldPos = mSelectedPosition;
+ mSelectedPosition = position;
+
+ if(position == -1){
+ if(mOnItemSelectedListener != null) mOnItemSelectedListener.onNothingSelected(this);
+ return;
+ }
+
+ v = getSelectedView();
+ if(v != null) v.setSelected(true);
+
+ if(oldPos != mSelectedPosition && mOnItemSelectedListener != null) mOnItemSelectedListener.onItemSelected(this, v, mSelectedPosition, getSelectedItemId());
+ }
+
+
+ private void reset() {
+ scrollTo(0, 0);
+ removeAllViewsInLayout();
+ mFirstItemPosition = 0;
+ mLastItemPosition = -1;
+ mLeftChildEdge = 0;
+ }
+
+
+ @Override
+ public void computeScroll() {
+ // if we don't have an adapter, we don't need to do anything
+ if (mAdapter == null) {
+ return;
+ }
+ if(mAdapter.getCount() == 0){
+ return;
+ }
+
+ if (mScroller.computeScrollOffset()) {
+ if(mScroller.getFinalX() == mScroller.getCurrX()){
+ mScroller.abortAnimation();
+ mTouchState = TOUCH_STATE_RESTING;
+ if(!checkScrollPosition())
+ clearChildrenCache();
+ return;
+ }
+
+ int x = mScroller.getCurrX();
+ scrollTo(x, 0);
+
+ postInvalidate();
+ }
+ else if(mTouchState == TOUCH_STATE_FLING || mTouchState == TOUCH_STATE_DISTANCE_SCROLL){
+ mTouchState = TOUCH_STATE_RESTING;
+ if(!checkScrollPosition())
+ clearChildrenCache();
+ }
+
+ if(mDataChanged){
+ removeAllViewsInLayout();
+ refillOnChange(mFirstItemPosition);
+ return;
+ }
+
+ relayout();
+ removeNonVisibleViews();
+ refillRight();
+ refillLeft();
+
+ }
+
+ /**
+ *
+ * @param velocityY The initial velocity in the Y direction. Positive
+ * numbers mean that the finger/cursor is moving down the screen,
+ * which means we want to scroll towards the top.
+ * @param velocityX The initial velocity in the X direction. Positive
+ * numbers mean that the finger/cursor is moving right the screen,
+ * which means we want to scroll towards the top.
+ */
+ public void fling(int velocityX, int velocityY){
+ mTouchState = TOUCH_STATE_FLING;
+ final int x = getScrollX();
+ final int y = getScrollY();
+
+ mScroller.fling(x, y, velocityX, velocityY, Integer.MIN_VALUE,Integer.MAX_VALUE, Integer.MIN_VALUE,Integer.MAX_VALUE);
+
+ invalidate();
+ }
+
+ /**
+ * Scroll widget by given distance in pixels
+ * @param dx
+ */
+ public void scroll(int dx){
+ mScroller.startScroll(getScrollX(), 0, dx, 0, SCROLLING_DURATION);
+ mTouchState = TOUCH_STATE_DISTANCE_SCROLL;
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right,
+ int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ // if we don't have an adapter, we don't need to do anything
+ if (mAdapter == null) {
+ return;
+ }
+
+ refillInternal(mLastItemPosition,mFirstItemPosition);
+ }
+
+ /**
+ * Method for actualizing content after data change in adapter. It is expected container was emptied before
+ * @param firstItemPosition
+ */
+ protected void refillOnChange(int firstItemPosition){
+ refillInternal(firstItemPosition-1, firstItemPosition);
+ }
+
+
+ protected void refillInternal(final int lastItemPos,final int firstItemPos){
+ // if we don't have an adapter, we don't need to do anything
+ if (mAdapter == null) {
+ return;
+ }
+ if(mAdapter.getCount() == 0){
+ return;
+ }
+
+ if(getChildCount() == 0){
+ fillFirstTime(lastItemPos, firstItemPos);
+ }
+ else{
+ relayout();
+ removeNonVisibleViews();
+ refillRight();
+ refillLeft();
+ }
+ }
+
+ /**
+ * Check if container visible area is filled and refill empty areas
+ */
+ private void refill(){
+ scrollTo(0, 0);
+ refillInternal(-1, 0);
+ }
+
+// protected void measureChild(View child, LoopLayoutParams params){
+// //prepare spec for measurement
+// final int specW, specH;
+//
+// specW = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.UNSPECIFIED), 0, params.width);
+// specH = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.UNSPECIFIED), 0, params.height);
+//
+////// final boolean useMeasuredW, useMeasuredH;
+//// if(params.height >= 0){
+//// specH = MeasureSpec.EXACTLY | params.height;
+////// useMeasuredH = false;
+//// }
+//// else{
+//// if(params.height == LayoutParams.MATCH_PARENT){
+//// specH = MeasureSpec.EXACTLY | getHeight();
+//// params.height = getHeight();
+////// useMeasuredH = false;
+//// }else{
+//// specH = MeasureSpec.AT_MOST | getHeight();
+////// useMeasuredH = true;
+//// }
+//// }
+////
+//// if(params.width >= 0){
+//// specW = MeasureSpec.EXACTLY | params.width;
+////// useMeasuredW = false;
+//// }
+//// else{
+//// if(params.width == LayoutParams.MATCH_PARENT){
+//// specW = MeasureSpec.EXACTLY | getWidth();
+//// params.width = getWidth();
+////// useMeasuredW = false;
+//// }else{
+//// specW = MeasureSpec.UNSPECIFIED;
+////// useMeasuredW = true;
+//// }
+//// }
+//
+// //measure
+// child.measure(specW, specH);
+// //put measured values into layout params from where they will be used in layout.
+// //Use measured values only if exact values was not specified in layout params.
+//// if(useMeasuredH) params.actualHeight = child.getMeasuredHeight();
+//// else params.actualHeight = params.height;
+////
+//// if(useMeasuredW) params.actualWidth = child.getMeasuredWidth();
+//// else params.actualWidth = params.width;
+// }
+
+ protected void measureChild(View child){
+ final int pwms = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
+ final int phms = MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY);
+ measureChild(child, pwms, phms);
+ }
+
+ private void relayout(){
+ final int c = getChildCount();
+ int left = mLeftChildEdge;
+
+ View child;
+ LoopLayoutParams lp;
+ for(int i = 0; i < c; i++){
+ child = getChildAt(i);
+ lp = (LoopLayoutParams) child.getLayoutParams();
+ measureChild(child);
+
+ left = layoutChildHorizontal(child, left, lp);
+ }
+
+ }
+
+
+ protected void fillFirstTime(final int lastItemPos,final int firstItemPos){
+ final int leftScreenEdge = 0;
+ final int rightScreenEdge = leftScreenEdge + getWidth();
+
+ int right;
+ int left;
+ View child;
+
+ boolean isRepeatingNow = false;
+
+ //scrolling is enabled until we find out we don't have enough items
+ isSrollingDisabled = false;
+
+ mLastItemPosition = lastItemPos;
+ mFirstItemPosition = firstItemPos;
+ mLeftChildEdge = 0;
+ right = mLeftChildEdge;
+ left = mLeftChildEdge;
+
+ while(right < rightScreenEdge){
+ mLastItemPosition++;
+
+ if(isRepeatingNow && mLastItemPosition >= firstItemPos) return;
+
+ if(mLastItemPosition >= mAdapter.getCount()){
+ if(firstItemPos == 0 && shouldRepeat) mLastItemPosition = 0;
+ else{
+ if(firstItemPos > 0){
+ mLastItemPosition = 0;
+ isRepeatingNow = true;
+ }
+ else if(!shouldRepeat){
+ mLastItemPosition--;
+ isSrollingDisabled = true;
+ final int w = right-mLeftChildEdge;
+ final int dx = (getWidth() - w)/2;
+ scrollTo(-dx, 0);
+ return;
+ }
+
+ }
+ }
+
+ if(mLastItemPosition >= mAdapter.getCount() ){
+ Log.wtf("EndlessLoop", "mLastItemPosition > mAdapter.getCount()");
+ return;
+ }
+
+ child = mAdapter.getView(mLastItemPosition, getCachedView(), this);
+ Validate.notNull(child, "Your adapter has returned null from getView.");
+ child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_AFTER);
+ left = layoutChildHorizontal(child, left, (LoopLayoutParams) child.getLayoutParams());
+ right = child.getRight();
+
+ //if selected view is going to screen, set selected state on him
+ if(mLastItemPosition == mSelectedPosition){
+ child.setSelected(true);
+ }
+
+ }
+
+ if(mScrollPositionIfEndless > 0){
+ final int p = mScrollPositionIfEndless;
+ mScrollPositionIfEndless = -1;
+ removeAllViewsInLayout();
+ refillOnChange(p);
+ }
+ }
+
+
+ /**
+ * Checks and refills empty area on the right
+ */
+ protected void refillRight(){
+ if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override override first init to scrolling disabled by falling to this branch
+ if(getChildCount() == 0) return;
+
+ final int leftScreenEdge = getScrollX();
+ final int rightScreenEdge = leftScreenEdge + getWidth();
+
+ View child = getChildAt(getChildCount() - 1);
+ int right = child.getRight();
+ int currLayoutLeft = right + ((LoopLayoutParams)child.getLayoutParams()).rightMargin;
+ while(right < rightScreenEdge){
+ mLastItemPosition++;
+ if(mLastItemPosition >= mAdapter.getCount()) mLastItemPosition = 0;
+
+ child = mAdapter.getView(mLastItemPosition, getCachedView(), this);
+ Validate.notNull(child,"Your adapter has returned null from getView.");
+ child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_AFTER);
+ currLayoutLeft = layoutChildHorizontal(child, currLayoutLeft, (LoopLayoutParams) child.getLayoutParams());
+ right = child.getRight();
+
+ //if selected view is going to screen, set selected state on him
+ if(mLastItemPosition == mSelectedPosition){
+ child.setSelected(true);
+ }
+ }
+ }
+
+ /**
+ * Checks and refills empty area on the left
+ */
+ protected void refillLeft(){
+ if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override first init to scrolling disabled by falling to this branch
+ if(getChildCount() == 0) return;
+
+ final int leftScreenEdge = getScrollX();
+
+ View child = getChildAt(0);
+ int childLeft = child.getLeft();
+ int currLayoutRight = childLeft - ((LoopLayoutParams)child.getLayoutParams()).leftMargin;
+ while(currLayoutRight > leftScreenEdge){
+ mFirstItemPosition--;
+ if(mFirstItemPosition < 0) mFirstItemPosition = mAdapter.getCount()-1;
+
+ child = mAdapter.getView(mFirstItemPosition, getCachedView(), this);
+ Validate.notNull(child,"Your adapter has returned null from getView.");
+ child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_TO_BEFORE);
+ currLayoutRight = layoutChildHorizontalToBefore(child, currLayoutRight, (LoopLayoutParams) child.getLayoutParams());
+ childLeft = child.getLeft() - ((LoopLayoutParams)child.getLayoutParams()).leftMargin;
+ //update left edge of children in container
+ mLeftChildEdge = childLeft;
+
+ //if selected view is going to screen, set selected state on him
+ if(mFirstItemPosition == mSelectedPosition){
+ child.setSelected(true);
+ }
+ }
+ }
+
+// /**
+// * Checks and refills empty area on the left
+// */
+// protected void refillLeft(){
+// if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override override first init to scrolling disabled by falling to this branch
+// final int leftScreenEdge = getScrollX();
+//
+// View child = getChildAt(0);
+// int currLayoutRight = child.getRight();
+// while(currLayoutRight > leftScreenEdge){
+// mFirstItemPosition--;
+// if(mFirstItemPosition < 0) mFirstItemPosition = mAdapter.getCount()-1;
+//
+// child = mAdapter.getView(mFirstItemPosition, getCachedView(mFirstItemPosition), this);
+// child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_TO_BEFORE);
+// currLayoutRight = layoutChildHorizontalToBefore(child, currLayoutRight, (LoopLayoutParams) child.getLayoutParams());
+//
+// //update left edge of children in container
+// mLeftChildEdge = child.getLeft();
+//
+// //if selected view is going to screen, set selected state on him
+// if(mFirstItemPosition == mSelectedPosition){
+// child.setSelected(true);
+// }
+// }
+// }
+
+ /**
+ * Removes view that are outside of the visible part of the list. Will not
+ * remove all views.
+ */
+ protected void removeNonVisibleViews() {
+ if(getChildCount() == 0) return;
+
+ final int leftScreenEdge = getScrollX();
+ final int rightScreenEdge = leftScreenEdge + getWidth();
+
+ // check if we should remove any views in the left
+ View firstChild = getChildAt(0);
+ final int leftedge = firstChild.getLeft() - ((LoopLayoutParams)firstChild.getLayoutParams()).leftMargin;
+ if(leftedge != mLeftChildEdge) throw new IllegalStateException("firstChild.getLeft() != mLeftChildEdge");
+ while (firstChild != null && firstChild.getRight() + ((LoopLayoutParams)firstChild.getLayoutParams()).rightMargin < leftScreenEdge) {
+ //if selected view is going off screen, remove selected state
+ firstChild.setSelected(false);
+
+ // remove view
+ removeViewInLayout(firstChild);
+
+ if(mViewObserver != null) mViewObserver.onViewRemovedFromParent(firstChild, mFirstItemPosition);
+ WeakReference ref = new WeakReference(firstChild);
+ mCachedItemViews.addLast(ref);
+
+ mFirstItemPosition++;
+ if(mFirstItemPosition >= mAdapter.getCount()) mFirstItemPosition = 0;
+
+ // update left item position
+ mLeftChildEdge = getChildAt(0).getLeft() - ((LoopLayoutParams)getChildAt(0).getLayoutParams()).leftMargin;
+
+ // Continue to check the next child only if we have more than
+ // one child left
+ if (getChildCount() > 1) {
+ firstChild = getChildAt(0);
+ } else {
+ firstChild = null;
+ }
+ }
+
+ // check if we should remove any views in the right
+ View lastChild = getChildAt(getChildCount() - 1);
+ while (lastChild != null && firstChild!=null && lastChild.getLeft() - ((LoopLayoutParams)firstChild.getLayoutParams()).leftMargin > rightScreenEdge) {
+ //if selected view is going off screen, remove selected state
+ lastChild.setSelected(false);
+
+ // remove the right view
+ removeViewInLayout(lastChild);
+
+ if(mViewObserver != null) mViewObserver.onViewRemovedFromParent(lastChild, mLastItemPosition);
+ WeakReference ref = new WeakReference(lastChild);
+ mCachedItemViews.addLast(ref);
+
+ mLastItemPosition--;
+ if(mLastItemPosition < 0) mLastItemPosition = mAdapter.getCount()-1;
+
+ // Continue to check the next child only if we have more than
+ // one child left
+ if (getChildCount() > 1) {
+ lastChild = getChildAt(getChildCount() - 1);
+ } else {
+ lastChild = null;
+ }
+ }
+ }
+
+
+ /**
+ * Adds a view as a child view and takes care of measuring it
+ *
+ * @param child The view to add
+ * @param layoutMode Either LAYOUT_MODE_LEFT or LAYOUT_MODE_RIGHT
+ * @return child which was actually added to container, subclasses can override to introduce frame views
+ */
+ protected View addAndMeasureChildHorizontal(final View child, final int layoutMode) {
+ LayoutParams lp = child.getLayoutParams();
+ LoopLayoutParams params;
+ if (lp == null) {
+ params = createLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+ else{
+ if(lp!=null && lp instanceof LoopLayoutParams) params = (LoopLayoutParams) lp;
+ else params = createLayoutParams(lp);
+ }
+ final int index = layoutMode == LAYOUT_MODE_TO_BEFORE ? 0 : -1;
+ addViewInLayout(child, index, params, true);
+
+ measureChild(child);
+ child.setDrawingCacheEnabled(true);
+
+ return child;
+ }
+
+
+
+ /**
+ * Layouts children from left to right
+ * @param left positon for left edge in parent container
+ * @param lp layout params
+ * @return new left
+ */
+ protected int layoutChildHorizontal(View v,int left, LoopLayoutParams lp){
+ int l,t,r,b;
+
+ switch(lp.position){
+ case LoopLayoutParams.TOP:
+ l = left + lp.leftMargin;
+ t = lp.topMargin;
+ r = l + v.getMeasuredWidth();
+ b = t + v.getMeasuredHeight();
+ break;
+ case LoopLayoutParams.BOTTOM:
+ b = getHeight() - lp.bottomMargin;
+ t = b - v.getMeasuredHeight();
+ l = left + lp.leftMargin;
+ r = l + v.getMeasuredWidth();
+ break;
+ case LoopLayoutParams.CENTER:
+ l = left + lp.leftMargin;
+ r = l + v.getMeasuredWidth();
+ final int x = (getHeight() - v.getMeasuredHeight())/2;
+ t = x;
+ b = t + v.getMeasuredHeight();
+ break;
+ default:
+ throw new RuntimeException("Only TOP,BOTTOM,CENTER are alowed in horizontal orientation");
+ }
+
+
+ v.layout(l, t, r, b);
+ return r + lp.rightMargin;
+ }
+
+ /**
+ * Layout children from right to left
+ */
+ protected int layoutChildHorizontalToBefore(View v,int right , LoopLayoutParams lp){
+ final int left = right - v.getMeasuredWidth() - lp.leftMargin - lp.rightMargin;
+ layoutChildHorizontal(v, left, lp);
+ return left;
+ }
+
+ /**
+ * Allows to make scroll alignments
+ * @return true if invalidate() was issued, and container is going to scroll
+ */
+ protected boolean checkScrollPosition(){
+ return false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onTouchEvent will be called and we do the actual
+ * scrolling there.
+ */
+
+
+ /*
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) && (mTouchState == TOUCH_STATE_SCROLLING)) {
+ return true;
+ }
+
+ final float x = ev.getX();
+ final float y = ev.getY();
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ //if we have scrolling disabled, we don't do anything
+ if(!shouldRepeat && isSrollingDisabled) return false;
+
+ /*
+ * not dragging, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+
+ /*
+ * Locally do absolute value. mLastMotionX is set to the x value
+ * of the down event.
+ */
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+ final int touchSlop = mTouchSlop;
+ final boolean xMoved = xDiff > touchSlop;
+ final boolean yMoved = yDiff > touchSlop;
+
+ if (xMoved) {
+
+ // Scroll if the user moved far enough along the X axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ mHandleSelectionOnActionUp = false;
+ enableChildrenCache();
+
+ // Either way, cancel any pending longpress
+ if (mAllowLongPress) {
+ mAllowLongPress = false;
+ // Try canceling the long press. It could also have been scheduled
+ // by a distant descendant, so use the mAllowLongPress flag to block
+ // everything
+ cancelLongPress();
+ }
+ }
+ if(yMoved){
+ mHandleSelectionOnActionUp = false;
+ if (mAllowLongPress) {
+ mAllowLongPress = false;
+ cancelLongPress();
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ // Remember location of down touch
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mAllowLongPress = true;
+// mCancelInIntercept = false;
+
+ mDown.x = (int) x;
+ mDown.y = (int) y;
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTING : TOUCH_STATE_SCROLLING;
+ //if he had normal click in rested state, remember for action up check
+ if(mTouchState == TOUCH_STATE_RESTING){
+ mHandleSelectionOnActionUp = true;
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ mDown.x = -1;
+ mDown.y = -1;
+// mCancelInIntercept = true;
+ break;
+ case MotionEvent.ACTION_UP:
+ //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
+ if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
+ final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
+ if((ev.getEventTime() - ev.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
+ }
+ // Release the drag
+ mAllowLongPress = false;
+ mHandleSelectionOnActionUp = false;
+ mDown.x = -1;
+ mDown.y = -1;
+ if(mTouchState == TOUCH_STATE_SCROLLING){
+ if(checkScrollPosition()){
+ break;
+ }
+ }
+ mTouchState = TOUCH_STATE_RESTING;
+ clearChildrenCache();
+ break;
+ }
+
+ mInterceptTouchEvents = mTouchState == TOUCH_STATE_SCROLLING;
+ return mInterceptTouchEvents;
+
+ }
+
+// /**
+// * Allow subclasses to override this to always intercept events
+// * @return
+// */
+// protected boolean interceptEvents(){
+// /*
+// * The only time we want to intercept motion events is if we are in the
+// * drag mode.
+// */
+// return mTouchState == TOUCH_STATE_SCROLLING;
+// }
+
+ protected void handleClick(Point p){
+ final int c = getChildCount();
+ View v;
+ final Rect r = new Rect();
+ for(int i=0; i < c; i++){
+ v = getChildAt(i);
+ v.getHitRect(r);
+ if(r.contains(getScrollX() + p.x, getScrollY() + p.y)){
+ final View old = getSelectedView();
+ if(old != null) old.setSelected(false);
+
+ int position = mFirstItemPosition + i;
+ if(position >= mAdapter.getCount()) position = position - mAdapter.getCount();
+
+
+ mSelectedPosition = position;
+ v.setSelected(true);
+
+ if(mOnItemClickListener != null) mOnItemClickListener.onItemClick(this, v, position , getItemIdAtPosition(position));
+ if(mOnItemSelectedListener != null) mOnItemSelectedListener.onItemSelected(this, v, position, getItemIdAtPosition(position));
+
+ break;
+ }
+ }
+ }
+
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // if we don't have an adapter, we don't need to do anything
+ if (mAdapter == null) {
+ return false;
+ }
+
+
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+
+ final int action = event.getAction();
+ final float x = event.getX();
+ final float y = event.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ super.onTouchEvent(event);
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.forceFinished(true);
+ }
+
+ // Remember where the motion event started
+ mLastMotionX = x;
+ mLastMotionY = y;
+
+ break;
+ case MotionEvent.ACTION_MOVE:
+ //if we have scrolling disabled, we don't do anything
+ if(!shouldRepeat && isSrollingDisabled) return false;
+
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ // Scroll to follow the motion event
+ final int deltaX = (int) (mLastMotionX - x);
+ mLastMotionX = x;
+ mLastMotionY = y;
+
+ int sx = getScrollX() + deltaX;
+
+ scrollTo(sx, 0);
+
+ }
+ else{
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+
+ final int touchSlop = mTouchSlop;
+ final boolean xMoved = xDiff > touchSlop;
+
+
+ if (xMoved) {
+
+ // Scroll if the user moved far enough along the X axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ enableChildrenCache();
+
+ // Either way, cancel any pending longpress
+ if (mAllowLongPress) {
+ mAllowLongPress = false;
+ // Try canceling the long press. It could also have been scheduled
+ // by a distant descendant, so use the mAllowLongPress flag to block
+ // everything
+ cancelLongPress();
+ }
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+
+ //this must be here, in case no child view returns true,
+ //events will propagate back here and on intercept touch event wont be called again
+ //in case of no parent it propagates here, in case of parent it usualy propagates to on cancel
+ if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
+ final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
+ if((event.getEventTime() - event.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
+ mHandleSelectionOnActionUp = false;
+ }
+
+ //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialXVelocity = (int) mVelocityTracker.getXVelocity();
+ int initialYVelocity = (int) mVelocityTracker.getYVelocity();
+
+ if (Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) {
+ fling(-initialXVelocity, -initialYVelocity);
+ }
+ else{
+ // Release the drag
+ clearChildrenCache();
+ mTouchState = TOUCH_STATE_RESTING;
+ checkScrollPosition();
+ mAllowLongPress = false;
+
+ mDown.x = -1;
+ mDown.y = -1;
+ }
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+
+ break;
+ }
+
+ // Release the drag
+ clearChildrenCache();
+ mTouchState = TOUCH_STATE_RESTING;
+ mAllowLongPress = false;
+
+ mDown.x = -1;
+ mDown.y = -1;
+
+ break;
+ case MotionEvent.ACTION_CANCEL:
+
+ //this must be here, in case no child view returns true,
+ //events will propagate back here and on intercept touch event wont be called again
+ //instead we get cancel here, since we stated we shouldn't intercept events and propagate them to children
+ //but events propagated back here, because no child was interested
+// if(!mInterceptTouchEvents && mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
+// handleClick(mDown);
+// mHandleSelectionOnActionUp = false;
+// }
+
+ mAllowLongPress = false;
+
+ mDown.x = -1;
+ mDown.y = -1;
+
+ if(mTouchState == TOUCH_STATE_SCROLLING){
+ if(checkScrollPosition()){
+ break;
+ }
+ }
+
+ mTouchState = TOUCH_STATE_RESTING;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ checkScrollFocusLeft();
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ checkScrollFocusRight();
+ break;
+ default:
+ break;
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ /**
+ * Moves with scroll window if focus hits one view before end of screen
+ */
+ private void checkScrollFocusLeft(){
+ final View focused = getFocusedChild();
+ if(getChildCount() >= 2 ){
+ View second = getChildAt(1);
+ View first = getChildAt(0);
+
+ if(focused == second){
+ scroll(-first.getWidth());
+ }
+ }
+ }
+
+ private void checkScrollFocusRight(){
+ final View focused = getFocusedChild();
+ if(getChildCount() >= 2 ){
+ View last = getChildAt(getChildCount()-1);
+ View lastButOne = getChildAt(getChildCount()-2);
+
+ if(focused == lastButOne){
+ scroll(last.getWidth());
+ }
+ }
+ }
+
+ /**
+ * Check if list of weak references has any view still in memory to offer for recyclation
+ * @return cached view
+ */
+ protected View getCachedView(){
+ if (mCachedItemViews.size() != 0) {
+ View v;
+ do{
+ v = mCachedItemViews.removeFirst().get();
+ }
+ while(v == null && mCachedItemViews.size() != 0);
+ return v;
+ }
+ return null;
+ }
+
+ protected void enableChildrenCache() {
+ setChildrenDrawnWithCacheEnabled(true);
+ setChildrenDrawingCacheEnabled(true);
+ }
+
+ protected void clearChildrenCache() {
+ setChildrenDrawnWithCacheEnabled(false);
+ }
+
+ @Override
+ public void setOnItemClickListener(
+ OnItemClickListener listener) {
+ mOnItemClickListener = listener;
+ }
+
+ @Override
+ public void setOnItemSelectedListener(
+ OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ }
+
+ @Override
+ @CapturedViewProperty
+ public int getSelectedItemPosition() {
+ return mSelectedPosition;
+ }
+
+ /**
+ * Only set value for selection position field, no gui updates are done
+ * for setting selection with gui updates and callback calls use setSelection
+ * @param position
+ */
+ public void setSeletedItemPosition(int position){
+ if(mAdapter.getCount() == 0 && position == 0) position = -1;
+ if(position < -1 || position > mAdapter.getCount()-1)
+ throw new IllegalArgumentException("Position index must be in range of adapter values (0 - getCount()-1) or -1 to unselect");
+
+ mSelectedPosition = position;
+ }
+
+ @Override
+ @CapturedViewProperty
+ public long getSelectedItemId() {
+ return mAdapter.getItemId(mSelectedPosition);
+ }
+
+ @Override
+ public Object getSelectedItem() {
+ return getSelectedView();
+ }
+
+ @Override
+ @CapturedViewProperty
+ public int getCount() {
+ if(mAdapter != null) return mAdapter.getCount();
+ else return 0;
+ }
+
+ @Override
+ public int getPositionForView(View view) {
+ final int c = getChildCount();
+ View v;
+ for(int i = 0; i < c; i++){
+ v = getChildAt(i);
+ if(v == view) return mFirstItemPosition + i;
+ }
+ return INVALID_POSITION;
+ }
+
+ @Override
+ public int getFirstVisiblePosition() {
+ return mFirstItemPosition;
+ }
+
+ @Override
+ public int getLastVisiblePosition() {
+ return mLastItemPosition;
+ }
+
+ @Override
+ public Object getItemAtPosition(int position) {
+ final int index;
+ if(mFirstItemPosition > position){
+ index = position + mAdapter.getCount() - mFirstItemPosition;
+ }
+ else{
+ index = position - mFirstItemPosition;
+ }
+ if(index < 0 || index >= getChildCount()) return null;
+
+ return getChildAt(index);
+ }
+
+ @Override
+ public long getItemIdAtPosition(int position) {
+ return mAdapter.getItemId(position);
+ }
+
+ @Override
+ public boolean performItemClick(View view, int position, long id) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ public void setViewObserver(IViewObserver viewObserver) {
+ this.mViewObserver = viewObserver;
+ }
+
+
+}
+
+
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/FeatureCoverFlow.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/FeatureCoverFlow.java
similarity index 95%
rename from MAComponents/src/com/martinappl/components/ui/containers/FeatureCoverFlow.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/FeatureCoverFlow.java
index de2f2f4..aa2c3d6 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/FeatureCoverFlow.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/FeatureCoverFlow.java
@@ -1,1447 +1,1486 @@
-/**
- *
- */
-package com.martinappl.components.ui.containers;
-
-import java.lang.ref.WeakReference;
-import java.util.LinkedList;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Camera;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.LinearGradient;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Shader.TileMode;
-import android.support.v4.util.LruCache;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Display;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.WindowManager;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.FrameLayout;
-import android.widget.Scroller;
-
-import com.martinappl.components.R;
-import com.martinappl.components.general.Validate;
-
-
-/**
- * @author Martin Appl
- * Note: Supports wrap content for height
- *
- */
-public class FeatureCoverFlow extends EndlessLoopAdapterContainer implements ViewTreeObserver.OnPreDrawListener {
- public static final int DEFAULT_MAX_CACHE_SIZE = 32;
-
- /**
- * Graphics Camera used for generating transformation matrices;
- */
- private final Camera mCamera = new Camera();
- /**
- * Relative spacing value of Views in container. If <1 Views will overlap, if >1 Views will have spaces between them
- */
- private float mSpacing = 0.5f;
-
- /**
- * Index of view in center of screen, which is most in foreground
- */
- private int mReverseOrderIndex = -1;
-
- private int mLastCenterItemIndex = -1;
-
- /**
- * Distance from center as fraction of half of widget size where covers start to rotate into center
- * 1 means rotation starts on edge of widget, 0 means only center rotated
- */
- private float mRotationThreshold = 0.3f;
-
- /**
- * Distance from center as fraction of half of widget size where covers start to zoom in
- * 1 means scaling starts on edge of widget, 0 means only center scaled
- */
- private float mScalingThreshold = 0.3f;
-
- /**
- * Distance from center as fraction of half of widget size,
- * where covers start enlarge their spacing to allow for smooth passing each other without jumping over each other
- * 1 means edge of widget, 0 means only center
- */
- private float mAdjustPositionThreshold = 0.1f;
-
- /**
- * By enlarging this value, you can enlarge spacing in center of widget done by position adjustment
- */
- private float mAdjustPositionMultiplier = 1.0f;
-
- /**
- * Absolute value of rotation angle of cover at edge of widget in degrees
- */
- private float mMaxRotationAngle = 70.0f;
-
- /**
- * Scale factor of item in center
- */
- private float mMaxScaleFactor = 1.2f;
-
- /**
- * Radius of circle path which covers follow. Range of screen is -1 to 1, minimal radius is therefore 1
- */
- private float mRadius = 2f;
-
- /**
- * Radius of circle path which covers follow in coordinate space of matrix transformation. Used to scale offset
- */
- private float mRadiusInMatrixSpace = 1000f;
-
- /**
- * Size of reflection as a fraction of original image (0-1)
- */
- private float mReflectionHeight = 0.5f;
-
- /**
- * Gap between reflection and original image in pixels
- */
- private int mReflectionGap = 2;
-
- /**
- * Starting opacity of reflection. Reflection fades from this value to transparency;
- */
- private int mReflectionOpacity = 0x70;
-
- /**
- * Widget size on which was tuning of parameters done. This value is used to scale parameters on when widgets has different size
- */
- private int mTuningWidgetSize = 1280;
-
- /**
- * How long will alignment animation take
- */
- private int mAlignTime = 350;
-
- /**
- * If you don't want reflections to be transparent, you can set them background of same color as widget background
- */
- private int mReflectionBackgroundColor = Color.TRANSPARENT;
-
- /** A list of cached (re-usable) cover frames */
- protected final LinkedList> mRecycledCoverFrames = new LinkedList>();
-
- private int mPaddingTop = 0;
- private int mPaddingBottom = 0;
-
- private int mCenterItemOffset;
- private final Scroller mAlignScroller = new Scroller(getContext(), new DecelerateInterpolator());
-
- private final MyCache mCachedFrames;
-
- private int mCoverWidth = 160;
- private int mCoverHeight = 240;
-
- private final Matrix mMatrix = new Matrix();
- private final Matrix mTemp = new Matrix();
- private final Matrix mTempHit = new Matrix();
- private final Rect mTempRect = new Rect();
- private final RectF mTouchRect = new RectF();
-
- private View mMotionTarget;
- private float mTargetLeft;
- private float mTargetTop;
-
- //reflection
- private final Matrix mReflectionMatrix = new Matrix();
- private final Paint mPaint = new Paint();
- private final Paint mReflectionPaint = new Paint();
- private final PorterDuffXfermode mXfermode = new PorterDuffXfermode(Mode.DST_IN);
- private final Canvas mReflectionCanvas = new Canvas();
-
- private int mScrollToPositionOnNextInvalidate = -1;
-
-
- private boolean mInvalidated = false;
-
-
- private class MyCache extends LruCache{
-
- public MyCache(int maxSize) {
- super(maxSize);
- }
-
- @Override
- protected void entryRemoved(boolean evicted, Integer key, CoverFrame oldValue, CoverFrame newValue) {
- if(evicted){
- if(oldValue.getChildCount() == 1){
- mCachedItemViews.addLast(new WeakReference(oldValue.getChildAt(0)));
- recycleCoverFrame(oldValue); // removes children, must be after caching children
- }
- }
- }
-
- }
-
- public FeatureCoverFlow(Context context, AttributeSet attrs, int defStyle, int cacheSize) {
- super(context, attrs, defStyle);
-
- if(cacheSize <= 0) cacheSize = DEFAULT_MAX_CACHE_SIZE;
- mCachedFrames = new MyCache(cacheSize);
-
- setChildrenDrawingOrderEnabled(true);
- setChildrenDrawingCacheEnabled(true);
- setChildrenDrawnWithCacheEnabled(true);
-
- mReflectionMatrix.preScale(1.0f, -1.0f);
-
- //init params from xml
- if(attrs != null){
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FeatureCoverFlow, defStyle, 0);
-
- mCoverWidth = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_coverWidth, mCoverWidth);
- if(mCoverWidth % 2 == 1) mCoverWidth--;
- mCoverHeight = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_coverHeight,mCoverHeight);
- mSpacing = a.getFloat(R.styleable.FeatureCoverFlow_spacing, mSpacing);
- mRotationThreshold = a.getFloat(R.styleable.FeatureCoverFlow_rotationThreshold, mRotationThreshold);
- mScalingThreshold = a.getFloat(R.styleable.FeatureCoverFlow_scalingThreshold, mScalingThreshold);
- mAdjustPositionThreshold = a.getFloat(R.styleable.FeatureCoverFlow_adjustPositionThreshold, mAdjustPositionThreshold);
- mAdjustPositionMultiplier = a.getFloat(R.styleable.FeatureCoverFlow_adjustPositionMultiplier, mAdjustPositionMultiplier);
- mMaxRotationAngle = a.getFloat(R.styleable.FeatureCoverFlow_maxRotationAngle, mMaxRotationAngle);
- mMaxScaleFactor = a.getFloat(R.styleable.FeatureCoverFlow_maxScaleFactor, mMaxScaleFactor);
- mRadius = a.getFloat(R.styleable.FeatureCoverFlow_circlePathRadius, mRadius);
- mRadiusInMatrixSpace = a.getFloat(R.styleable.FeatureCoverFlow_circlePathRadiusInMatrixSpace, mRadiusInMatrixSpace);
- mReflectionHeight = a.getFloat(R.styleable.FeatureCoverFlow_reflectionHeight, mReflectionHeight);
- mReflectionGap = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_reflectionGap, mReflectionGap);
- mReflectionOpacity = a.getInteger(R.styleable.FeatureCoverFlow_reflectionOpacity, mReflectionOpacity);
- mTuningWidgetSize = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_tunningWidgetSize, mTuningWidgetSize);
- mAlignTime = a.getInteger(R.styleable.FeatureCoverFlow_alignAnimationTime, mAlignTime);
- mPaddingTop = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_verticalPaddingTop, mPaddingTop);
- mPaddingBottom = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_verticalPaddingBottom, mPaddingBottom);
- mReflectionBackgroundColor = a.getColor(R.styleable.FeatureCoverFlow_reflectionBackroundColor, Color.TRANSPARENT);
-
- a.recycle();
- }
- }
-
- public FeatureCoverFlow(Context context, AttributeSet attrs) {
- this(context, attrs,0);
- }
-
- public FeatureCoverFlow(Context context) {
- this(context,null);
- }
-
- public FeatureCoverFlow(Context context, int cacheSize) {
- this(context,null,0,cacheSize);
- }
-
- public FeatureCoverFlow(Context context, AttributeSet attrs, int defStyle) {
- this(context, attrs, defStyle, DEFAULT_MAX_CACHE_SIZE);
- }
-
-
- private class CoverFrame extends FrameLayout{
- private Bitmap mReflectionCache;
- private boolean mReflectionCacheInvalid = true;
-
-
- public CoverFrame(Context context, View cover) {
- super(context);
- setCover(cover);
- }
-
- public void setCover(View cover){
- if(cover.getLayoutParams() != null) setLayoutParams(cover.getLayoutParams());
-
- final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- lp.leftMargin = 1;
- lp.topMargin = 1;
- lp.rightMargin = 1;
- lp.bottomMargin = 1;
-
- if (cover.getParent()!=null && cover.getParent() instanceof ViewGroup) {
- ViewGroup parent = (ViewGroup) cover.getParent();
- parent.removeView(cover);
- }
-
- //register observer to catch cover redraws
- cover.getViewTreeObserver().addOnPreDrawListener(FeatureCoverFlow.this);
-
- addView(cover,lp);
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
- mReflectionCacheInvalid = true;
- }
-
-
- @Override
- public Bitmap getDrawingCache(boolean autoScale) {
- final Bitmap b = super.getDrawingCache(autoScale);
-
- if(mReflectionCacheInvalid){
- if((mTouchState != TOUCH_STATE_FLING && mTouchState != TOUCH_STATE_ALIGN) || mReflectionCache == null){
- try{
- mReflectionCache = createReflectionBitmap(b);
- mReflectionCacheInvalid = false;
- }
- catch (NullPointerException e){
- Log.e(VIEW_LOG_TAG, "Null pointer in createReflectionBitmap. Bitmap b=" + b, e);
- }
- }
- }
- return b;
- }
-
- public void recycle(){
- if(mReflectionCache != null){
- mReflectionCache.recycle();
- mReflectionCache = null;
- }
-
- mReflectionCacheInvalid = true;
- removeAllViewsInLayout();
- }
-
- }
-
-
- private float getWidgetSizeMultiplier(){
- return ((float)mTuningWidgetSize)/((float)getWidth());
- }
-
- @SuppressLint("NewApi")
- @Override
- protected View addAndMeasureChildHorizontal(View child, int layoutMode) {
- final int index = layoutMode == LAYOUT_MODE_TO_BEFORE ? 0 : -1;
- final LoopLayoutParams lp = new LoopLayoutParams(mCoverWidth, mCoverHeight);
-
- if(child!=null && child instanceof CoverFrame){
- addViewInLayout(child, index, lp, true);
- measureChild(child);
- return child;
- }
-
-
- CoverFrame frame = getRecycledCoverFrame();
- if(frame == null){
- frame = new CoverFrame(getContext(), child);
- }
- else{
- frame.setCover(child);
- }
-
- //to enable drawing cache
- if(android.os.Build.VERSION.SDK_INT >= 11) frame.setLayerType(LAYER_TYPE_SOFTWARE, null);
- frame.setDrawingCacheEnabled(true);
-
-
- addViewInLayout(frame, index, lp, true);
- measureChild(frame);
- return frame;
- }
-
- @Override
- protected int layoutChildHorizontal(View v, int left, LoopLayoutParams lp) {
- int l,t,r,b;
-
- l = left;
- r = l + v.getMeasuredWidth();
- final int x = ((getHeight() - mPaddingTop - mPaddingBottom) - v.getMeasuredHeight())/2 + mPaddingTop; // - (int)((lp.actualHeight*mReflectionHeight)/2)
- t = x;
- b = t + v.getMeasuredHeight();
-
- v.layout(l, t, r, b);
- return l + (int)(v.getMeasuredWidth() * mSpacing);
- }
-
- /**
- * Layout children from right to left
- */
- protected int layoutChildHorizontalToBefore(View v,int right , LoopLayoutParams lp){
- int left = right - v.getMeasuredWidth();;
- left = layoutChildHorizontal(v, left, lp);
- return left;
- }
-
- private int getChildsCenter(View v){
- final int w = v.getRight() - v.getLeft();
- return v.getLeft() + w/2;
- }
-
- private int getChildsCenter(int i){
- return getChildsCenter(getChildAt(i));
- }
-
-
- @Override
- protected int getChildDrawingOrder(int childCount, int i) {
- final int screenCenter = getWidth()/2 + getScrollX();
- final int myCenter = getChildsCenter(i);
- final int d = myCenter - screenCenter;
-
- final View v = getChildAt(i);
- final int sz = (int) (mSpacing * v.getWidth()/2f);
-
- if(mReverseOrderIndex == -1 && (Math.abs(d) < sz || d >= 0)){
- mReverseOrderIndex = i;
- mCenterItemOffset = d;
- mLastCenterItemIndex = i;
- return childCount-1;
- }
-
- if(mReverseOrderIndex == -1){
- return i;
- }
- else{
- if(i == childCount-1) {
- final int x = mReverseOrderIndex;
- mReverseOrderIndex = -1;
- return x;
- }
- return childCount - 1 - (i-mReverseOrderIndex);
- }
- }
-
-
- @Override
- protected void refillInternal(int lastItemPos, int firstItemPos) {
- super.refillInternal(lastItemPos, firstItemPos);
-
- final int c = getChildCount();
- for(int i=0; i < c; i++){
- getChildDrawingOrder(c, i); //go through children to fill center item offset
- }
-
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- mInvalidated = false; //last invalidate which marked redrawInProgress, caused this dispatchDraw. Clear flag to prevent creating loop
-
- mReverseOrderIndex = -1;
-
- canvas.getClipBounds(mTempRect);
- mTempRect.top = 0;
- mTempRect.bottom = getHeight();
- canvas.clipRect(mTempRect);
-
-
- super.dispatchDraw(canvas);
-
- if(mScrollToPositionOnNextInvalidate != -1 && mAdapter != null && mAdapter.getCount() > 0){
- final int lastCenterItemPosition = (mFirstItemPosition + mLastCenterItemIndex) % mAdapter.getCount();
- final int di = lastCenterItemPosition - mScrollToPositionOnNextInvalidate;
- mScrollToPositionOnNextInvalidate = -1;
- if(di != 0){
- final int dst = (int) (di * mCoverWidth * mSpacing) - mCenterItemOffset;
- scrollBy(-dst, 0);
- shouldRepeat = true;
- postInvalidate();
- return;
- }
- }
-
- //make sure we never stay unaligned after last draw in resting state
- if(mTouchState == TOUCH_STATE_RESTING && mCenterItemOffset != 0){
- scrollBy(mCenterItemOffset, 0);
- postInvalidate();
- }
-
- try {
- View v = getChildAt(mLastCenterItemIndex);
- if(v != null) v.requestFocus(FOCUS_FORWARD);
- }
- catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
-
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- scroll((int) (-1 * mCoverWidth * mSpacing) - mCenterItemOffset);
- return true;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- scroll((int) (mCoverWidth * mSpacing) - mCenterItemOffset);
- return true;
- default:
- break;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- protected void fillFirstTime(final int lastItemPos,final int firstItemPos){
- final int leftScreenEdge = 0;
- final int rightScreenEdge = leftScreenEdge + getWidth();
-
- int right;
- int left;
- View child;
-
- boolean isRepeatingNow = false;
-
- //scrolling is enabled until we find out we don't have enough items
- isSrollingDisabled = false;
-
- mLastItemPosition = lastItemPos;
- mFirstItemPosition = firstItemPos;
- mLeftChildEdge = (int) (-mCoverWidth * mSpacing);
- right = 0;
- left = mLeftChildEdge;
-
- while(right < rightScreenEdge){
- mLastItemPosition++;
-
- if(isRepeatingNow && mLastItemPosition >= firstItemPos) return;
-
- if(mLastItemPosition >= mAdapter.getCount()){
- if(firstItemPos == 0 && shouldRepeat) mLastItemPosition = 0;
- else{
- if(firstItemPos > 0){
- mLastItemPosition = 0;
- isRepeatingNow = true;
- }
- else if(!shouldRepeat){
- mLastItemPosition--;
- isSrollingDisabled = true;
- final int w = right-mLeftChildEdge;
- final int dx = (getWidth() - w)/2;
- scrollTo(-dx, 0);
- return;
- }
-
- }
- }
-
- if(mLastItemPosition >= mAdapter.getCount() ){
- Log.wtf("EndlessLoop", "mLastItemPosition > mAdapter.getCount()");
- return;
- }
-
- child = mAdapter.getView(mLastItemPosition, getCachedView(), this);
- Validate.notNull(child, "Your adapter has returned null from getView.");
- child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_AFTER);
- left = layoutChildHorizontal(child, left, (LoopLayoutParams) child.getLayoutParams());
- right = child.getRight();
-
- //if selected view is going to screen, set selected state on him
- if(mLastItemPosition == mSelectedPosition){
- child.setSelected(true);
- }
-
- }
-
- if(mScrollPositionIfEndless > 0){
- final int p = mScrollPositionIfEndless;
- mScrollPositionIfEndless = -1;
- removeAllViewsInLayout();
- refillOnChange(p);
- }
- }
-
- /**
- * Checks and refills empty area on the right
- */
- @Override
- protected void refillRight(){
- if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override override first init to scrolling disabled by falling to this branch
- if(getChildCount() == 0) return;
-
- final int leftScreenEdge = getScrollX();
- final int rightScreenEdge = leftScreenEdge + getWidth();
-
- View child = getChildAt(getChildCount() - 1);
- int currLayoutLeft = child.getLeft() + (int)(child.getWidth() * mSpacing);
- while(currLayoutLeft < rightScreenEdge){
- mLastItemPosition++;
- if(mLastItemPosition >= mAdapter.getCount()) mLastItemPosition = 0;
-
- child = getViewAtPosition(mLastItemPosition);
- child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_AFTER);
- currLayoutLeft = layoutChildHorizontal(child, currLayoutLeft, (LoopLayoutParams) child.getLayoutParams());
-
- //if selected view is going to screen, set selected state on him
- if(mLastItemPosition == mSelectedPosition){
- child.setSelected(true);
- }
- }
- }
-
-
- private boolean containsView(View v){
- for(int i=0; i < getChildCount(); i++){
- if(getChildAt(i) == v){
- return true;
- }
- }
- return false;
- }
-
-
-
- private View getViewAtPosition(int position){
- View v = mCachedFrames.remove(position);
- if(v == null){
- v = mAdapter.getView(position, getCachedView(), this);
- Validate.notNull(v,"Your adapter has returned null from getView.");
- return v;
- }
-
- if(!containsView(v)){
- return v;
- }
- else{
- v = mAdapter.getView(position, getCachedView(), this);
- Validate.notNull(v,"Your adapter has returned null from getView.");
- return v;
- }
- }
-
- /**
- * Checks and refills empty area on the left
- */
- @Override
- protected void refillLeft(){
- if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override override first init to scrolling disabled by falling to this branch
- if(getChildCount() == 0) return;
-
- final int leftScreenEdge = getScrollX();
-
- View child = getChildAt(0);
- int currLayoutRight = child.getRight() - (int)(child.getWidth() * mSpacing);
- while(currLayoutRight > leftScreenEdge){
- mFirstItemPosition--;
- if(mFirstItemPosition < 0) mFirstItemPosition = mAdapter.getCount()-1;
-
- child = getViewAtPosition(mFirstItemPosition);
- if(child == getChildAt(getChildCount() - 1)){
- removeViewInLayout(child);
- }
- child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_TO_BEFORE);
- currLayoutRight = layoutChildHorizontalToBefore(child, currLayoutRight, (LoopLayoutParams) child.getLayoutParams());
-
- //update left edge of children in container
- mLeftChildEdge = child.getLeft();
-
- //if selected view is going to screen, set selected state on him
- if(mFirstItemPosition == mSelectedPosition){
- child.setSelected(true);
- }
- }
- }
-
- /**
- * Removes view that are outside of the visible part of the list. Will not
- * remove all views.
- */
- protected void removeNonVisibleViews() {
- if(getChildCount() == 0) return;
-
- final int leftScreenEdge = getScrollX();
- final int rightScreenEdge = leftScreenEdge + getWidth();
-
- // check if we should remove any views in the left
- View firstChild = getChildAt(0);
- final int leftedge = firstChild.getLeft();
- if(leftedge != mLeftChildEdge) {
- Log.e("feature component", "firstChild.getLeft() != mLeftChildEdge, leftedge:" + leftedge + " ftChildEdge:"+ mLeftChildEdge);
- View v = getChildAt(0);
- removeAllViewsInLayout();
- addAndMeasureChildHorizontal(v,LAYOUT_MODE_TO_BEFORE);
- layoutChildHorizontal(v, mLeftChildEdge, (LoopLayoutParams) v.getLayoutParams());
- return;
- }
- while (firstChild != null && firstChild.getRight() < leftScreenEdge) {
- //if selected view is going off screen, remove selected state
- firstChild.setSelected(false);
-
- // remove view
- removeViewInLayout(firstChild);
-
- mCachedFrames.put(mFirstItemPosition, (CoverFrame) firstChild);
-
- mFirstItemPosition++;
- if(mFirstItemPosition >= mAdapter.getCount()) mFirstItemPosition = 0;
-
- // update left item position
- mLeftChildEdge = getChildAt(0).getLeft();
-
- // Continue to check the next child only if we have more than
- // one child left
- if (getChildCount() > 1) {
- firstChild = getChildAt(0);
- } else {
- firstChild = null;
- }
- }
-
- // check if we should remove any views in the right
- View lastChild = getChildAt(getChildCount() - 1);
- while (lastChild != null && lastChild.getLeft() > rightScreenEdge) {
- //if selected view is going off screen, remove selected state
- lastChild.setSelected(false);
-
- // remove the right view
- removeViewInLayout(lastChild);
-
- mCachedFrames.put(mLastItemPosition, (CoverFrame) lastChild);
-
- mLastItemPosition--;
- if(mLastItemPosition < 0) mLastItemPosition = mAdapter.getCount()-1;
-
- // Continue to check the next child only if we have more than
- // one child left
- if (getChildCount() > 1) {
- lastChild = getChildAt(getChildCount() - 1);
- } else {
- lastChild = null;
- }
- }
- }
-
-
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- canvas.save();
-
- //set matrix to child's transformation
- setChildTransformation(child, mMatrix);
-
- //Generate child bitmap
- Bitmap bitmap = child.getDrawingCache();
-
- //initialize canvas state. Child 0,0 coordinates will match canvas 0,0
- canvas.translate(child.getLeft(), child.getTop());
-
-
-
- //set child transformation on canvas
- canvas.concat(mMatrix);
-
- final Bitmap rfCache = ((CoverFrame) child).mReflectionCache;
-
- if(mReflectionBackgroundColor != Color.TRANSPARENT){
- final int top = bitmap.getHeight() + mReflectionGap - 2;
- final float frame = 1.0f;
- mReflectionPaint.setColor(mReflectionBackgroundColor);
- canvas.drawRect(frame, top + frame , rfCache.getWidth()-frame, top + rfCache.getHeight() - frame, mReflectionPaint);
- }
-
- mPaint.reset();
- mPaint.setAntiAlias(true);
- mPaint.setFilterBitmap(true);
-
- //Draw child bitmap with applied transforms
- canvas.drawBitmap(bitmap, 0.0f, 0.0f, mPaint);
-
- //Draw reflection
- canvas.drawBitmap(rfCache, 0.0f, bitmap.getHeight() - 2 + mReflectionGap, mPaint);
-
-
- canvas.restore();
- return false;
- }
-
- private Bitmap createReflectionBitmap(Bitmap original){
- final int w = original.getWidth();
- final int h = original.getHeight();
- final int rh = (int) (h * mReflectionHeight);
- final int gradientColor = Color.argb(mReflectionOpacity, 0xff, 0xff, 0xff);
-
- final Bitmap reflection = Bitmap.createBitmap(original, 0, rh, w, rh, mReflectionMatrix, false);
-
- final LinearGradient shader = new LinearGradient(0, 0, 0, reflection.getHeight(), gradientColor, 0x00ffffff,TileMode.CLAMP);
- mPaint.reset();
- mPaint.setShader(shader);
- mPaint.setXfermode(mXfermode);
-
- mReflectionCanvas.setBitmap(reflection);
- mReflectionCanvas.drawRect(0, 0, reflection.getWidth(), reflection.getHeight(), mPaint);
-
- return reflection;
- }
-
- /**
- * Fill outRect with transformed child hit rectangle. Rectangle is not moved to its position on screen, neither getSroolX is accounted for
- * @param child
- * @param outRect
- */
- protected void transformChildHitRectangle(View child, RectF outRect){
- outRect.left = 0;
- outRect.top = 0;
- outRect.right = child.getWidth();
- outRect.bottom = child.getHeight();
-
- setChildTransformation(child, mTempHit);
- mTempHit.mapRect(outRect);
- }
-
- protected void transformChildHitRectangle(View child, RectF outRect, final Matrix transformation){
- outRect.left = 0;
- outRect.top = 0;
- outRect.right = child.getWidth();
- outRect.bottom = child.getHeight();
-
- transformation.mapRect(outRect);
- }
-
- private void setChildTransformation(View child, Matrix m){
- m.reset();
-
- addChildRotation(child, m);
- addChildScale(child, m);
- addChildCircularPathZOffset(child, m);
- addChildAdjustPosition(child,m);
-
- //set coordinate system origin to center of child
- m.preTranslate(-child.getWidth()/2f, -child.getHeight()/2f);
- //move back
- m.postTranslate(child.getWidth()/2f, child.getHeight()/2f);
-
- }
-
-
- private void addChildCircularPathZOffset(View child, Matrix m){
- mCamera.save();
-
- final float v = getOffsetOnCircle(getChildsCenter(child));
- final float z = mRadiusInMatrixSpace * v;
-
- mCamera.translate(0.0f, 0.0f, z);
-
- mCamera.getMatrix(mTemp);
- m.postConcat(mTemp);
-
- mCamera.restore();
- }
-
-
- private void addChildScale(View v,Matrix m){
- final float f = getScaleFactor(getChildsCenter(v));
- m.postScale(f, f);
- }
-
- private void addChildRotation(View v, Matrix m){
- mCamera.save();
-
- final int c = getChildsCenter(v);
- mCamera.rotateY(getRotationAngle(c) - getAngleOnCircle(c));
-
- mCamera.getMatrix(mTemp);
- m.postConcat(mTemp);
-
- mCamera.restore();
- }
-
- private void addChildAdjustPosition(View child, Matrix m) {
- final int c = getChildsCenter(child);
- final float crp = getClampedRelativePosition(getRelativePosition(c), mAdjustPositionThreshold * getWidgetSizeMultiplier());
- final float d = mCoverWidth * mAdjustPositionMultiplier * mSpacing * crp * getSpacingMultiplierOnCirlce(c);
-
- m.postTranslate(d, 0f);
- }
-
- /**
- * Calculates relative position on screen in range -1 to 1, widgets out of screen can have values ove 1 or -1
- * @param pixexPos Absolute position in pixels including scroll offset
- * @return relative position
- */
- private float getRelativePosition(int pixexPos){
- final int half = getWidth()/2;
- final int centerPos = getScrollX() + half;
-
- return (pixexPos - centerPos)/((float) half);
- }
-
- /**
- * Clamps relative position by threshold, and produces values in range -1 to 1 directly usable for transformation computation
- * @param position value int range -1 to 1
- * @param treshold always positive value of threshold distance from center in range 0-1
- * @return
- */
- private float getClampedRelativePosition(float position, float threshold){
- if(position < 0){
- if(position < -threshold) return -1f;
- else return position/threshold;
- }
- else{
- if(position > threshold) return 1;
- else return position/threshold;
- }
- }
-
- private float getRotationAngle(int childCenter){
- return -mMaxRotationAngle * getClampedRelativePosition(getRelativePosition(childCenter), mRotationThreshold * getWidgetSizeMultiplier());
- }
-
- private float getScaleFactor(int childCenter){
- return 1 + (mMaxScaleFactor-1) * (1 - Math.abs(getClampedRelativePosition(getRelativePosition(childCenter), mScalingThreshold * getWidgetSizeMultiplier())));
- }
-
-
- /**
- * Compute offset following path on circle
- * @param childCenter
- * @return offset from position on unitary circle
- */
- private float getOffsetOnCircle(int childCenter){
- float x = getRelativePosition(childCenter)/mRadius;
- if(x < -1.0f) x = -1.0f;
- if(x > 1.0f) x = 1.0f;
-
- return (float) (1 - Math.sin(Math.acos(x)));
- }
-
- private float getAngleOnCircle(int childCenter){
- float x = getRelativePosition(childCenter)/mRadius;
- if(x < -1.0f) x = -1.0f;
- if(x > 1.0f) x = 1.0f;
-
- return (float) (Math.acos(x)/Math.PI*180.0f - 90.0f);
- }
-
- private float getSpacingMultiplierOnCirlce(int childCenter){
- float x = getRelativePosition(childCenter)/mRadius;
- return (float) Math.sin(Math.acos(x));
- }
-
-
-
- @Override
- protected void handleClick(Point p) {
- final int c = getChildCount();
- View v;
- final RectF r = new RectF();
- final int[] childOrder = new int[c];
-
-
- for(int i=0; i < c; i++){
- childOrder[i] = getChildDrawingOrder(c, i);
- }
-
- for(int i = c-1; i >= 0; i--){
- v = getChildAt(childOrder[i]); //we need reverse drawing order. Check children drawn last first
- getScrolledTransformedChildRectangle(v, r);
- if(r.contains(p.x,p.y)){
- final View old = getSelectedView();
- if(old != null) old.setSelected(false);
-
-
- int position = mFirstItemPosition + childOrder[i];
- if(position >= mAdapter.getCount()) position = position - mAdapter.getCount();
-
-
- mSelectedPosition = position;
- v.setSelected(true);
-
- if(mOnItemClickListener != null) mOnItemClickListener.onItemClick(this, v, position , getItemIdAtPosition(position));
- if(mOnItemSelectedListener != null) mOnItemSelectedListener.onItemSelected(this, v, position, getItemIdAtPosition(position));
-
-
- break;
- }
- }
- }
-
-
-
- @Override
- public void computeScroll() {
- // if we don't have an adapter, we don't need to do anything
- if (mAdapter == null) {
- return;
- }
- if(mAdapter.getCount() == 0){
- return;
- }
-
- if(getChildCount() == 0){ //release memory resources was probably called before, and onLayout didn't get called to fill container again
- requestLayout();
- }
-
- if (mTouchState == TOUCH_STATE_ALIGN) {
- if(mAlignScroller.computeScrollOffset()) {
- if(mAlignScroller.getFinalX() == mAlignScroller.getCurrX()){
- mAlignScroller.abortAnimation();
- mTouchState = TOUCH_STATE_RESTING;
- clearChildrenCache();
- return;
- }
-
- int x = mAlignScroller.getCurrX();
- scrollTo(x, 0);
-
- postInvalidate();
- return;
- }
- else{
- mTouchState = TOUCH_STATE_RESTING;
- clearChildrenCache();
- return;
- }
- }
-
- super.computeScroll();
- }
-
- @Override
- protected boolean checkScrollPosition() {
- if(mCenterItemOffset != 0){
- mAlignScroller.startScroll(getScrollX(), 0, mCenterItemOffset, 0, mAlignTime);
- mTouchState = TOUCH_STATE_ALIGN;
- invalidate();
- return true;
- }
- return false;
- }
-
- private void getScrolledTransformedChildRectangle(View child, RectF r){
- transformChildHitRectangle(child, r);
- final int offset = child.getLeft() - getScrollX();
- r.offset(offset, child.getTop());
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- final int action = ev.getAction();
- final float xf = ev.getX();
- final float yf = ev.getY();
- final RectF frame = mTouchRect;
-
- if (action == MotionEvent.ACTION_DOWN) {
- if (mMotionTarget != null) {
- // this is weird, we got a pen down, but we thought it was
- // already down!
- // We should probably send an ACTION_UP to the current
- // target.
- mMotionTarget = null;
- }
- // If we're disallowing intercept or if we're allowing and we didn't
- // intercept
- if (!onInterceptTouchEvent(ev)) {
- // reset this event's action (just to protect ourselves)
- ev.setAction(MotionEvent.ACTION_DOWN);
- // We know we want to dispatch the event down, find a child
- // who can handle it, start with the front-most child.
-
- final int count = getChildCount();
- final int[] childOrder = new int[count];
-
- for(int i=0; i < count; i++){
- childOrder[i] = getChildDrawingOrder(count, i);
- }
-
- for(int i = count-1; i >= 0; i--) {
- final View child = getChildAt(childOrder[i]);
- if (child.getVisibility() == VISIBLE
- || child.getAnimation() != null) {
-
- getScrolledTransformedChildRectangle(child, frame);
-
- if (frame.contains(xf, yf)) {
- // offset the event to the view's coordinate system
- final float xc = xf - frame.left;
- final float yc = yf - frame.top;
- ev.setLocation(xc, yc);
- if (child.dispatchTouchEvent(ev)) {
- // Event handled, we have a target now.
- mMotionTarget = child;
- mTargetTop = frame.top;
- mTargetLeft = frame.left;
- return true;
- }
-
- break;
- }
- }
- }
- }
- }
-
- boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
- (action == MotionEvent.ACTION_CANCEL);
-
-
- // The event wasn't an ACTION_DOWN, dispatch it to our target if
- // we have one.
- final View target = mMotionTarget;
- if (target == null) {
- // We don't have a target, this means we're handling the
- // event as a regular view.
- ev.setLocation(xf, yf);
- return onTouchEvent(ev);
- }
-
- // if have a target, see if we're allowed to and want to intercept its
- // events
- if (onInterceptTouchEvent(ev)) {
- final float xc = xf - mTargetLeft;
- final float yc = yf - mTargetTop;
- ev.setAction(MotionEvent.ACTION_CANCEL);
- ev.setLocation(xc, yc);
- if (!target.dispatchTouchEvent(ev)) {
- // target didn't handle ACTION_CANCEL. not much we can do
- // but they should have.
- }
- // clear the target
- mMotionTarget = null;
- // Don't dispatch this event to our own view, because we already
- // saw it when intercepting; we just want to give the following
- // event to the normal onTouchEvent().
- return true;
- }
-
- if (isUpOrCancel) {
- mMotionTarget = null;
- mTargetTop = -1;
- mTargetLeft = -1;
- }
-
- // finally offset the event to the target's coordinate system and
- // dispatch the event.
- final float xc = xf - mTargetLeft;
- final float yc = yf - mTargetTop;
- ev.setLocation(xc, yc);
-
- return target.dispatchTouchEvent(ev);
- }
-
-
- @SuppressWarnings("deprecation")
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
- final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
-
-
- int h,w;
- if(heightSpecMode == MeasureSpec.EXACTLY) h = heightSpecSize;
- else{
- h = (int) ((mCoverHeight + mCoverHeight*mReflectionHeight + mReflectionGap) * mMaxScaleFactor + mPaddingTop + mPaddingBottom);
- h = resolveSize(h, heightMeasureSpec);
- }
-
- if(widthSpecMode == MeasureSpec.EXACTLY) w = widthSpecSize;
- else{
- WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();
- w = display.getWidth();
- w = resolveSize(w, widthMeasureSpec);
- }
-
- setMeasuredDimension(w, h);
- }
-
-
- //disable turning caches of and on, we need them always on
- @Override
- protected void enableChildrenCache() {}
-
- @Override
- protected void clearChildrenCache() {}
-
- /**
- * How many items can remain in cache. Lower in case of memory issues
- * @param size number of cached covers
- */
- public void trimChacheSize(int size){
- mCachedFrames.trimToSize(size);
- }
-
- /**
- * Clear internal cover cache
- */
- public void clearCache(){
- mCachedFrames.evictAll();
- }
-
- /**
- * Returns widget spacing (as fraction of widget size)
- * @return Widgets spacing
- */
- public float getSpacing() {
- return mSpacing;
- }
-
- /**
- * Set widget spacing (float means fraction of widget size, 1 = widget size)
- * @param spacing the spacing to set
- */
- public void setSpacing(float spacing) {
- this.mSpacing = spacing;
- }
-
- /**
- * Return width of cover in pixels
- * @return the Cover Width
- */
- public int getCoverWidth() {
- return mCoverWidth;
- }
-
- /**
- * Set width of cover in pixels
- * @param coverWidth the Cover Width to set
- */
- public void setCoverWidth(int coverWidth) {
- if(coverWidth % 2 == 1) coverWidth--;
- this.mCoverWidth = coverWidth;
- }
-
- /**
- * Return cover height in pixels
- * @return the Cover Height
- */
- public int getCoverHeight() {
- return mCoverHeight;
- }
-
- /**
- * Set cover height in pixels
- * @param coverHeight the Cover Height to set
- */
- public void setCoverHeight(int coverHeight) {
- this.mCoverHeight = coverHeight;
- }
-
- /**
- * Sets distance from center as fraction of half of widget size where covers start to rotate into center
- * 1 means rotation starts on edge of widget, 0 means only center rotated
- * @param rotationThreshold the rotation threshold to set
- */
- public void setRotationTreshold(float rotationThreshold) {
- this.mRotationThreshold = rotationThreshold;
- }
-
- /**
- * Sets distance from center as fraction of half of widget size where covers start to zoom in
- * 1 means scaling starts on edge of widget, 0 means only center scaled
- * @param scalingThreshold the scaling threshold to set
- */
- public void setScalingThreshold(float scalingThreshold) {
- this.mScalingThreshold = scalingThreshold;
- }
-
- /**
- * Sets distance from center as fraction of half of widget size,
- * where covers start enlarge their spacing to allow for smooth passing each other without jumping over each other
- * 1 means edge of widget, 0 means only center
- * @param adjustPositionThreshold the adjust position threshold to set
- */
- public void setAdjustPositionThreshold(float adjustPositionThreshold) {
- this.mAdjustPositionThreshold = adjustPositionThreshold;
- }
-
- /**
- * Sets adjust position multiplier. By enlarging this value, you can enlarge spacing in center of widget done by position adjustment
- * @param adjustPositionMultiplier the adjust position multiplier to set
- */
- public void setAdjustPositionMultiplier(float adjustPositionMultiplier) {
- this.mAdjustPositionMultiplier = adjustPositionMultiplier;
- }
-
- /**
- * Sets absolute value of rotation angle of cover at edge of widget in degrees.
- * Rotation made by traveling around circle path is added to this value separately.
- * By enlarging this value you make covers more rotated. Max value without traveling on circle would be 90 degrees.
- * With small circle radius could go even over this value sometimes. Look depends also on other parameters.
- * @param maxRotationAngle the max rotation angle to set
- */
- public void setMaxRotationAngle(float maxRotationAngle) {
- this.mMaxRotationAngle = maxRotationAngle;
- }
-
- /**
- * Sets scale factor of item in center. Normal size is multiplied with this value
- * @param maxScaleFactor the max scale factor to set
- */
- public void setMaxScaleFactor(float maxScaleFactor) {
- this.mMaxScaleFactor = maxScaleFactor;
- }
-
- /**
- * Sets radius of circle path which covers follow. Range of screen is -1 to 1, minimal radius is therefore 1
- * This value affect how big part of circle path you see on screen and therefore how much away are covers at edge of screen.
- * And also how much they are rotated in direction of circle path.
- * @param radius the radius to set
- */
- public void setRadius(float radius) {
- this.mRadius = radius;
- }
-
- /**
- * This value affects how far are covers at the edges of widget in Z coordinate in matrix space
- * @param radiusInMatrixSpace the radius in matrix space to set
- */
- public void setRadiusInMatrixSpace(float radiusInMatrixSpace) {
- this.mRadiusInMatrixSpace = radiusInMatrixSpace;
- }
-
- /**
- * Reflection height as a fraction of cover height (1 means same size as original)
- * @param reflectionHeight the reflection height to set
- */
- public void setReflectionHeight(float reflectionHeight) {
- this.mReflectionHeight = reflectionHeight;
- }
-
- /**
- * @param reflectionGap Gap between original image and reflection in pixels
- */
- public void setReflectionGap(int reflectionGap) {
- this.mReflectionGap = reflectionGap;
- }
-
- /**
- * @param reflectionOpacity Opacity at most opaque part of reflection fade out effect
- */
- public void setReflectionOpacity(int reflectionOpacity) {
- this.mReflectionOpacity = reflectionOpacity;
- }
-
- /**
- * Widget size on which was tuning of parameters done. This value is used to scale parameters when widgets has different size
- * @param size returned by widgets getWidth()
- */
- public void setTuningWidgetSize(int size) {
- this.mTuningWidgetSize = size;
- }
-
- /**
- * @param alignTime How long takes center alignment animation in milliseconds
- */
- public void setAlignTime(int alignTime) {
- this.mAlignTime = alignTime;
- }
-
- /**
- * @param paddingTop
- */
- public void setVerticalPaddingTop(int paddingTop) {
- this.mPaddingTop = paddingTop;
- }
-
- public void setVerticalPaddingBottom(int paddingBottom) {
- this.mPaddingBottom = paddingBottom;
- }
-
-
- /**
- * Set this to some color if you don't want see through reflections other reflections. Preferably set to same color as background color
- * @param reflectionBackgroundColor the Reflection Background Color to set
- */
- public void setReflectionBackgroundColor(int reflectionBackgroundColor) {
- this.mReflectionBackgroundColor = reflectionBackgroundColor;
- }
-
- @Override
- /**
- * Get position of center item in adapter.
- * @return position of center item inside adapter date or -1 if there is no center item shown
- */
- public int getScrollPosition() {
- if(mAdapter == null || mAdapter.getCount() == 0) return -1;
-
- if(mLastCenterItemIndex != -1){
- return (mFirstItemPosition + mLastCenterItemIndex) % mAdapter.getCount();
- }
- else return (mFirstItemPosition + (getWidth()/((int)(mCoverWidth * mSpacing)))/2) % mAdapter.getCount();
- }
-
- /**
- * Set new center item position
- */
- @Override
- public void scrollToPosition(int position) {
- if(mAdapter == null || mAdapter.getCount() == 0) throw new IllegalStateException("You are trying to scroll container with no adapter set. Set adapter first.");
-
- if(mLastCenterItemIndex != -1){
- final int lastCenterItemPosition = (mFirstItemPosition + mLastCenterItemIndex) % mAdapter.getCount();
- final int di = lastCenterItemPosition - position;
- final int dst = (int) (di * mCoverWidth * mSpacing);
- mScrollToPositionOnNextInvalidate = -1;
- scrollBy(-dst, 0);
- }
- else{
- mScrollToPositionOnNextInvalidate = position;
- }
-
- invalidate();
- }
-
- /**
- * removes children, must be after caching children
- * @param cf
- */
- private void recycleCoverFrame(CoverFrame cf){
- cf.recycle();
- WeakReference ref = new WeakReference(cf);
- mRecycledCoverFrames.addLast(ref);
- }
-
- protected CoverFrame getRecycledCoverFrame(){
- if (!mRecycledCoverFrames.isEmpty()) {
- CoverFrame v;
- do{
- v = mRecycledCoverFrames.removeFirst().get();
- }
- while(v == null && !mRecycledCoverFrames.isEmpty());
- return v;
- }
- return null;
- }
-
- /**
- * Removes links to all pictures which are hold by coverflow to speed up rendering
- * Sets environment to state from which it can be refilled on next onLayout
- * Good place to release resources is in activitys onStop.
- */
- public void releaseAllMemoryResources(){
- mLastItemPosition = mFirstItemPosition;
- mLastItemPosition--;
-
- final int w = (int)(mCoverWidth*mSpacing);
- int sp = getScrollX() % w;
- if(sp < 0) sp = sp + w;
- scrollTo(sp, 0);
-
- removeAllViewsInLayout();
- clearCache();
- }
-
- @Override
- public boolean onPreDraw() { //when child view is about to be drawn we invalidate whole container
-
- if(!mInvalidated){ //this is hack, no idea now is possible that this works, but fixes problem where not all area was redrawn
- mInvalidated = true;
- invalidate();
- return false;
- }
-
- return true;
-
- }
-
-
-
-}
+/**
+ *
+ */
+package it.moondroid.coverflow.components.ui.containers;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Camera;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader.TileMode;
+import android.support.v4.util.LruCache;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.Scroller;
+
+import java.lang.ref.WeakReference;
+import java.util.LinkedList;
+
+import it.moondroid.coverflow.R;
+import it.moondroid.coverflow.components.general.Validate;
+
+
+/**
+ * @author Martin Appl
+ * Note: Supports wrap content for height
+ *
+ */
+public class FeatureCoverFlow extends EndlessLoopAdapterContainer implements ViewTreeObserver.OnPreDrawListener {
+ public static final int DEFAULT_MAX_CACHE_SIZE = 32;
+
+ /**
+ * Graphics Camera used for generating transformation matrices;
+ */
+ private final Camera mCamera = new Camera();
+ /**
+ * Relative spacing value of Views in container. If <1 Views will overlap, if >1 Views will have spaces between them
+ */
+ private float mSpacing = 0.5f;
+
+ /**
+ * Index of view in center of screen, which is most in foreground
+ */
+ private int mReverseOrderIndex = -1;
+
+ private int mLastCenterItemIndex = -1;
+
+ /**
+ * Distance from center as fraction of half of widget size where covers start to rotate into center
+ * 1 means rotation starts on edge of widget, 0 means only center rotated
+ */
+ private float mRotationThreshold = 0.3f;
+
+ /**
+ * Distance from center as fraction of half of widget size where covers start to zoom in
+ * 1 means scaling starts on edge of widget, 0 means only center scaled
+ */
+ private float mScalingThreshold = 0.3f;
+
+ /**
+ * Distance from center as fraction of half of widget size,
+ * where covers start enlarge their spacing to allow for smooth passing each other without jumping over each other
+ * 1 means edge of widget, 0 means only center
+ */
+ private float mAdjustPositionThreshold = 0.1f;
+
+ /**
+ * By enlarging this value, you can enlarge spacing in center of widget done by position adjustment
+ */
+ private float mAdjustPositionMultiplier = 1.0f;
+
+ /**
+ * Absolute value of rotation angle of cover at edge of widget in degrees
+ */
+ private float mMaxRotationAngle = 70.0f;
+
+ /**
+ * Scale factor of item in center
+ */
+ private float mMaxScaleFactor = 1.2f;
+
+ /**
+ * Radius of circle path which covers follow. Range of screen is -1 to 1, minimal radius is therefore 1
+ */
+ private float mRadius = 2f;
+
+ /**
+ * Radius of circle path which covers follow in coordinate space of matrix transformation. Used to scale offset
+ */
+ private float mRadiusInMatrixSpace = 1000f;
+
+ /**
+ * Size of reflection as a fraction of original image (0-1)
+ */
+ private float mReflectionHeight = 0.5f;
+
+ /**
+ * Gap between reflection and original image in pixels
+ */
+ private int mReflectionGap = 2;
+
+ /**
+ * Starting opacity of reflection. Reflection fades from this value to transparency;
+ */
+ private int mReflectionOpacity = 0x70;
+
+ /**
+ * Widget size on which was tuning of parameters done. This value is used to scale parameters on when widgets has different size
+ */
+ private int mTuningWidgetSize = 1280;
+
+ /**
+ * How long will alignment animation take
+ */
+ private int mAlignTime = 350;
+
+ /**
+ * If you don't want reflections to be transparent, you can set them background of same color as widget background
+ */
+ private int mReflectionBackgroundColor = Color.TRANSPARENT;
+
+ /** A list of cached (re-usable) cover frames */
+ protected final LinkedList> mRecycledCoverFrames = new LinkedList>();
+
+ /** A listener for center item position */
+ private OnScrollPositionListener mOnScrollPositionListener;
+
+ private int mLastTouchState = -1;
+ private int mlastCenterItemPosition = -1;
+
+ public interface OnScrollPositionListener {
+ public void onScrolledToPosition(int position);
+ public void onScrolling();
+ }
+
+ private int mPaddingTop = 0;
+ private int mPaddingBottom = 0;
+
+ private int mCenterItemOffset;
+ private final Scroller mAlignScroller = new Scroller(getContext(), new DecelerateInterpolator());
+
+ private final MyCache mCachedFrames;
+
+ private int mCoverWidth = 160;
+ private int mCoverHeight = 240;
+
+ private final Matrix mMatrix = new Matrix();
+ private final Matrix mTemp = new Matrix();
+ private final Matrix mTempHit = new Matrix();
+ private final Rect mTempRect = new Rect();
+ private final RectF mTouchRect = new RectF();
+
+ private View mMotionTarget;
+ private float mTargetLeft;
+ private float mTargetTop;
+
+ //reflection
+ private final Matrix mReflectionMatrix = new Matrix();
+ private final Paint mPaint = new Paint();
+ private final Paint mReflectionPaint = new Paint();
+ private final PorterDuffXfermode mXfermode = new PorterDuffXfermode(Mode.DST_IN);
+ private final Canvas mReflectionCanvas = new Canvas();
+
+ private int mScrollToPositionOnNextInvalidate = -1;
+
+
+ private boolean mInvalidated = false;
+
+
+ private class MyCache extends LruCache{
+
+ public MyCache(int maxSize) {
+ super(maxSize);
+ }
+
+ @Override
+ protected void entryRemoved(boolean evicted, Integer key, CoverFrame oldValue, CoverFrame newValue) {
+ if(evicted){
+ if(oldValue.getChildCount() == 1){
+ mCachedItemViews.addLast(new WeakReference(oldValue.getChildAt(0)));
+ recycleCoverFrame(oldValue); // removes children, must be after caching children
+ }
+ }
+ }
+
+ }
+
+ public FeatureCoverFlow(Context context, AttributeSet attrs, int defStyle, int cacheSize) {
+ super(context, attrs, defStyle);
+
+ if(cacheSize <= 0) cacheSize = DEFAULT_MAX_CACHE_SIZE;
+ mCachedFrames = new MyCache(cacheSize);
+
+ setChildrenDrawingOrderEnabled(true);
+ setChildrenDrawingCacheEnabled(true);
+ setChildrenDrawnWithCacheEnabled(true);
+
+ mReflectionMatrix.preScale(1.0f, -1.0f);
+
+ //init params from xml
+ if(attrs != null){
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FeatureCoverFlow, defStyle, 0);
+
+ mCoverWidth = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_coverWidth, mCoverWidth);
+ if(mCoverWidth % 2 == 1) mCoverWidth--;
+ mCoverHeight = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_coverHeight,mCoverHeight);
+ mSpacing = a.getFloat(R.styleable.FeatureCoverFlow_spacing, mSpacing);
+ mRotationThreshold = a.getFloat(R.styleable.FeatureCoverFlow_rotationThreshold, mRotationThreshold);
+ mScalingThreshold = a.getFloat(R.styleable.FeatureCoverFlow_scalingThreshold, mScalingThreshold);
+ mAdjustPositionThreshold = a.getFloat(R.styleable.FeatureCoverFlow_adjustPositionThreshold, mAdjustPositionThreshold);
+ mAdjustPositionMultiplier = a.getFloat(R.styleable.FeatureCoverFlow_adjustPositionMultiplier, mAdjustPositionMultiplier);
+ mMaxRotationAngle = a.getFloat(R.styleable.FeatureCoverFlow_maxRotationAngle, mMaxRotationAngle);
+ mMaxScaleFactor = a.getFloat(R.styleable.FeatureCoverFlow_maxScaleFactor, mMaxScaleFactor);
+ mRadius = a.getFloat(R.styleable.FeatureCoverFlow_circlePathRadius, mRadius);
+ mRadiusInMatrixSpace = a.getFloat(R.styleable.FeatureCoverFlow_circlePathRadiusInMatrixSpace, mRadiusInMatrixSpace);
+ mReflectionHeight = a.getFloat(R.styleable.FeatureCoverFlow_reflectionHeight, mReflectionHeight);
+ mReflectionGap = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_reflectionGap, mReflectionGap);
+ mReflectionOpacity = a.getInteger(R.styleable.FeatureCoverFlow_reflectionOpacity, mReflectionOpacity);
+ mTuningWidgetSize = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_tunningWidgetSize, mTuningWidgetSize);
+ mAlignTime = a.getInteger(R.styleable.FeatureCoverFlow_alignAnimationTime, mAlignTime);
+ mPaddingTop = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_verticalPaddingTop, mPaddingTop);
+ mPaddingBottom = a.getDimensionPixelSize(R.styleable.FeatureCoverFlow_verticalPaddingBottom, mPaddingBottom);
+ mReflectionBackgroundColor = a.getColor(R.styleable.FeatureCoverFlow_reflectionBackroundColor, Color.TRANSPARENT);
+
+ a.recycle();
+ }
+ }
+
+ public FeatureCoverFlow(Context context, AttributeSet attrs) {
+ this(context, attrs,0);
+ }
+
+ public FeatureCoverFlow(Context context) {
+ this(context,null);
+ }
+
+ public FeatureCoverFlow(Context context, int cacheSize) {
+ this(context,null,0,cacheSize);
+ }
+
+ public FeatureCoverFlow(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, DEFAULT_MAX_CACHE_SIZE);
+ }
+
+
+ private class CoverFrame extends FrameLayout{
+ private Bitmap mReflectionCache;
+ private boolean mReflectionCacheInvalid = true;
+
+
+ public CoverFrame(Context context, View cover) {
+ super(context);
+ setCover(cover);
+ }
+
+ public void setCover(View cover){
+ if(cover.getLayoutParams() != null) setLayoutParams(cover.getLayoutParams());
+
+ final LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ lp.leftMargin = 1;
+ lp.topMargin = 1;
+ lp.rightMargin = 1;
+ lp.bottomMargin = 1;
+
+ if (cover.getParent()!=null && cover.getParent() instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) cover.getParent();
+ parent.removeView(cover);
+ }
+
+ //register observer to catch cover redraws
+ cover.getViewTreeObserver().addOnPreDrawListener(FeatureCoverFlow.this);
+
+ addView(cover,lp);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ mReflectionCacheInvalid = true;
+ }
+
+
+ @Override
+ public Bitmap getDrawingCache(boolean autoScale) {
+ final Bitmap b = super.getDrawingCache(autoScale);
+
+ if(mReflectionCacheInvalid){
+ if((mTouchState != TOUCH_STATE_FLING && mTouchState != TOUCH_STATE_ALIGN) || mReflectionCache == null){
+ try{
+ mReflectionCache = createReflectionBitmap(b);
+ mReflectionCacheInvalid = false;
+ }
+ catch (NullPointerException e){
+ Log.e(VIEW_LOG_TAG, "Null pointer in createReflectionBitmap. Bitmap b=" + b, e);
+ }
+ }
+ }
+ return b;
+ }
+
+ public void recycle(){
+ if(mReflectionCache != null){
+ mReflectionCache.recycle();
+ mReflectionCache = null;
+ }
+
+ mReflectionCacheInvalid = true;
+ removeAllViewsInLayout();
+ }
+
+ }
+
+
+ private float getWidgetSizeMultiplier(){
+ return ((float)mTuningWidgetSize)/((float)getWidth());
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ protected View addAndMeasureChildHorizontal(View child, int layoutMode) {
+ final int index = layoutMode == LAYOUT_MODE_TO_BEFORE ? 0 : -1;
+ final LoopLayoutParams lp = new LoopLayoutParams(mCoverWidth, mCoverHeight);
+
+ if(child!=null && child instanceof CoverFrame){
+ addViewInLayout(child, index, lp, true);
+ measureChild(child);
+ return child;
+ }
+
+
+ CoverFrame frame = getRecycledCoverFrame();
+ if(frame == null){
+ frame = new CoverFrame(getContext(), child);
+ }
+ else{
+ frame.setCover(child);
+ }
+
+ //to enable drawing cache
+ if(android.os.Build.VERSION.SDK_INT >= 11) frame.setLayerType(LAYER_TYPE_SOFTWARE, null);
+ frame.setDrawingCacheEnabled(true);
+
+
+ addViewInLayout(frame, index, lp, true);
+ measureChild(frame);
+ return frame;
+ }
+
+ @Override
+ protected int layoutChildHorizontal(View v, int left, LoopLayoutParams lp) {
+ int l,t,r,b;
+
+ l = left;
+ r = l + v.getMeasuredWidth();
+ final int x = ((getHeight() - mPaddingTop - mPaddingBottom) - v.getMeasuredHeight())/2 + mPaddingTop; // - (int)((lp.actualHeight*mReflectionHeight)/2)
+ t = x;
+ b = t + v.getMeasuredHeight();
+
+ v.layout(l, t, r, b);
+ return l + (int)(v.getMeasuredWidth() * mSpacing);
+ }
+
+ /**
+ * Layout children from right to left
+ */
+ protected int layoutChildHorizontalToBefore(View v,int right , LoopLayoutParams lp){
+ int left = right - v.getMeasuredWidth();;
+ left = layoutChildHorizontal(v, left, lp);
+ return left;
+ }
+
+ private int getChildsCenter(View v){
+ final int w = v.getRight() - v.getLeft();
+ return v.getLeft() + w/2;
+ }
+
+ private int getChildsCenter(int i){
+ return getChildsCenter(getChildAt(i));
+ }
+
+
+ @Override
+ protected int getChildDrawingOrder(int childCount, int i) {
+ final int screenCenter = getWidth()/2 + getScrollX();
+ final int myCenter = getChildsCenter(i);
+ final int d = myCenter - screenCenter;
+
+ final View v = getChildAt(i);
+ final int sz = (int) (mSpacing * v.getWidth()/2f);
+
+ if(mReverseOrderIndex == -1 && (Math.abs(d) < sz || d >= 0)){
+ mReverseOrderIndex = i;
+ mCenterItemOffset = d;
+ mLastCenterItemIndex = i;
+ return childCount-1;
+ }
+
+ if(mReverseOrderIndex == -1){
+ return i;
+ }
+ else{
+ if(i == childCount-1) {
+ final int x = mReverseOrderIndex;
+ mReverseOrderIndex = -1;
+ return x;
+ }
+ return childCount - 1 - (i-mReverseOrderIndex);
+ }
+ }
+
+
+ @Override
+ protected void refillInternal(int lastItemPos, int firstItemPos) {
+ super.refillInternal(lastItemPos, firstItemPos);
+
+ final int c = getChildCount();
+ for(int i=0; i < c; i++){
+ getChildDrawingOrder(c, i); //go through children to fill center item offset
+ }
+
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ mInvalidated = false; //last invalidate which marked redrawInProgress, caused this dispatchDraw. Clear flag to prevent creating loop
+
+ mReverseOrderIndex = -1;
+
+ canvas.getClipBounds(mTempRect);
+ mTempRect.top = 0;
+ mTempRect.bottom = getHeight();
+ canvas.clipRect(mTempRect);
+
+
+ super.dispatchDraw(canvas);
+
+ if(mScrollToPositionOnNextInvalidate != -1 && mAdapter != null && mAdapter.getCount() > 0){
+ final int lastCenterItemPosition = (mFirstItemPosition + mLastCenterItemIndex) % mAdapter.getCount();
+ final int di = lastCenterItemPosition - mScrollToPositionOnNextInvalidate;
+ mScrollToPositionOnNextInvalidate = -1;
+ if(di != 0){
+ final int dst = (int) (di * mCoverWidth * mSpacing) - mCenterItemOffset;
+ scrollBy(-dst, 0);
+ shouldRepeat = true;
+ postInvalidate();
+ return;
+ }
+ }
+
+ if(mTouchState == TOUCH_STATE_RESTING){
+
+ final int lastCenterItemPosition = (mFirstItemPosition + mLastCenterItemIndex) % mAdapter.getCount();
+ if (mLastTouchState != TOUCH_STATE_RESTING || mlastCenterItemPosition != lastCenterItemPosition){
+ mLastTouchState = TOUCH_STATE_RESTING;
+ mlastCenterItemPosition = lastCenterItemPosition;
+ if(mOnScrollPositionListener != null) mOnScrollPositionListener.onScrolledToPosition(lastCenterItemPosition);
+ }
+ }
+
+ if (mTouchState == TOUCH_STATE_SCROLLING && mLastTouchState != TOUCH_STATE_SCROLLING){
+ mLastTouchState = TOUCH_STATE_SCROLLING;
+ if(mOnScrollPositionListener != null) mOnScrollPositionListener.onScrolling();
+ }
+ if (mTouchState == TOUCH_STATE_FLING && mLastTouchState != TOUCH_STATE_FLING){
+ mLastTouchState = TOUCH_STATE_FLING;
+ if(mOnScrollPositionListener != null) mOnScrollPositionListener.onScrolling();
+ }
+
+
+ //make sure we never stay unaligned after last draw in resting state
+ if(mTouchState == TOUCH_STATE_RESTING && mCenterItemOffset != 0){
+ scrollBy(mCenterItemOffset, 0);
+ postInvalidate();
+ }
+
+ try {
+ View v = getChildAt(mLastCenterItemIndex);
+ if(v != null) v.requestFocus(FOCUS_FORWARD);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ scroll((int) (-1 * mCoverWidth * mSpacing) - mCenterItemOffset);
+ return true;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ scroll((int) (mCoverWidth * mSpacing) - mCenterItemOffset);
+ return true;
+ default:
+ break;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ protected void fillFirstTime(final int lastItemPos,final int firstItemPos){
+ final int leftScreenEdge = 0;
+ final int rightScreenEdge = leftScreenEdge + getWidth();
+
+ int right;
+ int left;
+ View child;
+
+ boolean isRepeatingNow = false;
+
+ //scrolling is enabled until we find out we don't have enough items
+ isSrollingDisabled = false;
+
+ mLastItemPosition = lastItemPos;
+ mFirstItemPosition = firstItemPos;
+ mLeftChildEdge = (int) (-mCoverWidth * mSpacing);
+ right = 0;
+ left = mLeftChildEdge;
+
+ while(right < rightScreenEdge){
+ mLastItemPosition++;
+
+ if(isRepeatingNow && mLastItemPosition >= firstItemPos) return;
+
+ if(mLastItemPosition >= mAdapter.getCount()){
+ if(firstItemPos == 0 && shouldRepeat) mLastItemPosition = 0;
+ else{
+ if(firstItemPos > 0){
+ mLastItemPosition = 0;
+ isRepeatingNow = true;
+ }
+ else if(!shouldRepeat){
+ mLastItemPosition--;
+ isSrollingDisabled = true;
+ final int w = right-mLeftChildEdge;
+ final int dx = (getWidth() - w)/2;
+ scrollTo(-dx, 0);
+ return;
+ }
+
+ }
+ }
+
+ if(mLastItemPosition >= mAdapter.getCount() ){
+ Log.wtf("EndlessLoop", "mLastItemPosition > mAdapter.getCount()");
+ return;
+ }
+
+ child = mAdapter.getView(mLastItemPosition, getCachedView(), this);
+ Validate.notNull(child, "Your adapter has returned null from getView.");
+ child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_AFTER);
+ left = layoutChildHorizontal(child, left, (LoopLayoutParams) child.getLayoutParams());
+ right = child.getRight();
+
+ //if selected view is going to screen, set selected state on him
+ if(mLastItemPosition == mSelectedPosition){
+ child.setSelected(true);
+ }
+
+ }
+
+ if(mScrollPositionIfEndless > 0){
+ final int p = mScrollPositionIfEndless;
+ mScrollPositionIfEndless = -1;
+ removeAllViewsInLayout();
+ refillOnChange(p);
+ }
+ }
+
+ /**
+ * Checks and refills empty area on the right
+ */
+ @Override
+ protected void refillRight(){
+ if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override override first init to scrolling disabled by falling to this branch
+ if(getChildCount() == 0) return;
+
+ final int leftScreenEdge = getScrollX();
+ final int rightScreenEdge = leftScreenEdge + getWidth();
+
+ View child = getChildAt(getChildCount() - 1);
+ int currLayoutLeft = child.getLeft() + (int)(child.getWidth() * mSpacing);
+ while(currLayoutLeft < rightScreenEdge){
+ mLastItemPosition++;
+ if(mLastItemPosition >= mAdapter.getCount()) mLastItemPosition = 0;
+
+ child = getViewAtPosition(mLastItemPosition);
+ child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_AFTER);
+ currLayoutLeft = layoutChildHorizontal(child, currLayoutLeft, (LoopLayoutParams) child.getLayoutParams());
+
+ //if selected view is going to screen, set selected state on him
+ if(mLastItemPosition == mSelectedPosition){
+ child.setSelected(true);
+ }
+ }
+ }
+
+
+ private boolean containsView(View v){
+ for(int i=0; i < getChildCount(); i++){
+ if(getChildAt(i) == v){
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ private View getViewAtPosition(int position){
+ View v = mCachedFrames.remove(position);
+ if(v == null){
+ v = mAdapter.getView(position, getCachedView(), this);
+ Validate.notNull(v,"Your adapter has returned null from getView.");
+ return v;
+ }
+
+ if(!containsView(v)){
+ return v;
+ }
+ else{
+ v = mAdapter.getView(position, getCachedView(), this);
+ Validate.notNull(v,"Your adapter has returned null from getView.");
+ return v;
+ }
+ }
+
+ /**
+ * Checks and refills empty area on the left
+ */
+ @Override
+ protected void refillLeft(){
+ if(!shouldRepeat && isSrollingDisabled) return; //prevent next layout calls to override override first init to scrolling disabled by falling to this branch
+ if(getChildCount() == 0) return;
+
+ final int leftScreenEdge = getScrollX();
+
+ View child = getChildAt(0);
+ int currLayoutRight = child.getRight() - (int)(child.getWidth() * mSpacing);
+ while(currLayoutRight > leftScreenEdge){
+ mFirstItemPosition--;
+ if(mFirstItemPosition < 0) mFirstItemPosition = mAdapter.getCount()-1;
+
+ child = getViewAtPosition(mFirstItemPosition);
+ if(child == getChildAt(getChildCount() - 1)){
+ removeViewInLayout(child);
+ }
+ child = addAndMeasureChildHorizontal(child, LAYOUT_MODE_TO_BEFORE);
+ currLayoutRight = layoutChildHorizontalToBefore(child, currLayoutRight, (LoopLayoutParams) child.getLayoutParams());
+
+ //update left edge of children in container
+ mLeftChildEdge = child.getLeft();
+
+ //if selected view is going to screen, set selected state on him
+ if(mFirstItemPosition == mSelectedPosition){
+ child.setSelected(true);
+ }
+ }
+ }
+
+ /**
+ * Removes view that are outside of the visible part of the list. Will not
+ * remove all views.
+ */
+ protected void removeNonVisibleViews() {
+ if(getChildCount() == 0) return;
+
+ final int leftScreenEdge = getScrollX();
+ final int rightScreenEdge = leftScreenEdge + getWidth();
+
+ // check if we should remove any views in the left
+ View firstChild = getChildAt(0);
+ final int leftedge = firstChild.getLeft();
+ if(leftedge != mLeftChildEdge) {
+ Log.e("feature component", "firstChild.getLeft() != mLeftChildEdge, leftedge:" + leftedge + " ftChildEdge:"+ mLeftChildEdge);
+ View v = getChildAt(0);
+ removeAllViewsInLayout();
+ addAndMeasureChildHorizontal(v,LAYOUT_MODE_TO_BEFORE);
+ layoutChildHorizontal(v, mLeftChildEdge, (LoopLayoutParams) v.getLayoutParams());
+ return;
+ }
+ while (firstChild != null && firstChild.getRight() < leftScreenEdge) {
+ //if selected view is going off screen, remove selected state
+ firstChild.setSelected(false);
+
+ // remove view
+ removeViewInLayout(firstChild);
+
+ mCachedFrames.put(mFirstItemPosition, (CoverFrame) firstChild);
+
+ mFirstItemPosition++;
+ if(mFirstItemPosition >= mAdapter.getCount()) mFirstItemPosition = 0;
+
+ // update left item position
+ mLeftChildEdge = getChildAt(0).getLeft();
+
+ // Continue to check the next child only if we have more than
+ // one child left
+ if (getChildCount() > 1) {
+ firstChild = getChildAt(0);
+ } else {
+ firstChild = null;
+ }
+ }
+
+ // check if we should remove any views in the right
+ View lastChild = getChildAt(getChildCount() - 1);
+ while (lastChild != null && lastChild.getLeft() > rightScreenEdge) {
+ //if selected view is going off screen, remove selected state
+ lastChild.setSelected(false);
+
+ // remove the right view
+ removeViewInLayout(lastChild);
+
+ mCachedFrames.put(mLastItemPosition, (CoverFrame) lastChild);
+
+ mLastItemPosition--;
+ if(mLastItemPosition < 0) mLastItemPosition = mAdapter.getCount()-1;
+
+ // Continue to check the next child only if we have more than
+ // one child left
+ if (getChildCount() > 1) {
+ lastChild = getChildAt(getChildCount() - 1);
+ } else {
+ lastChild = null;
+ }
+ }
+ }
+
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ canvas.save();
+
+ //set matrix to child's transformation
+ setChildTransformation(child, mMatrix);
+
+ //Generate child bitmap
+ Bitmap bitmap = child.getDrawingCache();
+
+ //initialize canvas state. Child 0,0 coordinates will match canvas 0,0
+ canvas.translate(child.getLeft(), child.getTop());
+
+
+
+ //set child transformation on canvas
+ canvas.concat(mMatrix);
+
+ final Bitmap rfCache = ((CoverFrame) child).mReflectionCache;
+
+ if(mReflectionBackgroundColor != Color.TRANSPARENT){
+ final int top = bitmap.getHeight() + mReflectionGap - 2;
+ final float frame = 1.0f;
+ mReflectionPaint.setColor(mReflectionBackgroundColor);
+ canvas.drawRect(frame, top + frame , rfCache.getWidth()-frame, top + rfCache.getHeight() - frame, mReflectionPaint);
+ }
+
+ mPaint.reset();
+ mPaint.setAntiAlias(true);
+ mPaint.setFilterBitmap(true);
+
+ //Draw child bitmap with applied transforms
+ canvas.drawBitmap(bitmap, 0.0f, 0.0f, mPaint);
+
+ //Draw reflection
+ canvas.drawBitmap(rfCache, 0.0f, bitmap.getHeight() - 2 + mReflectionGap, mPaint);
+
+
+ canvas.restore();
+ return false;
+ }
+
+ private Bitmap createReflectionBitmap(Bitmap original){
+ final int w = original.getWidth();
+ final int h = original.getHeight();
+ final int rh = (int) (h * mReflectionHeight);
+ final int gradientColor = Color.argb(mReflectionOpacity, 0xff, 0xff, 0xff);
+
+ final Bitmap reflection = Bitmap.createBitmap(original, 0, rh, w, rh, mReflectionMatrix, false);
+
+ final LinearGradient shader = new LinearGradient(0, 0, 0, reflection.getHeight(), gradientColor, 0x00ffffff,TileMode.CLAMP);
+ mPaint.reset();
+ mPaint.setShader(shader);
+ mPaint.setXfermode(mXfermode);
+
+ mReflectionCanvas.setBitmap(reflection);
+ mReflectionCanvas.drawRect(0, 0, reflection.getWidth(), reflection.getHeight(), mPaint);
+
+ return reflection;
+ }
+
+ /**
+ * Fill outRect with transformed child hit rectangle. Rectangle is not moved to its position on screen, neither getSroolX is accounted for
+ * @param child
+ * @param outRect
+ */
+ protected void transformChildHitRectangle(View child, RectF outRect){
+ outRect.left = 0;
+ outRect.top = 0;
+ outRect.right = child.getWidth();
+ outRect.bottom = child.getHeight();
+
+ setChildTransformation(child, mTempHit);
+ mTempHit.mapRect(outRect);
+ }
+
+ protected void transformChildHitRectangle(View child, RectF outRect, final Matrix transformation){
+ outRect.left = 0;
+ outRect.top = 0;
+ outRect.right = child.getWidth();
+ outRect.bottom = child.getHeight();
+
+ transformation.mapRect(outRect);
+ }
+
+ private void setChildTransformation(View child, Matrix m){
+ m.reset();
+
+ addChildRotation(child, m);
+ addChildScale(child, m);
+ addChildCircularPathZOffset(child, m);
+ addChildAdjustPosition(child,m);
+
+ //set coordinate system origin to center of child
+ m.preTranslate(-child.getWidth()/2f, -child.getHeight()/2f);
+ //move back
+ m.postTranslate(child.getWidth()/2f, child.getHeight()/2f);
+
+ }
+
+
+ private void addChildCircularPathZOffset(View child, Matrix m){
+ mCamera.save();
+
+ final float v = getOffsetOnCircle(getChildsCenter(child));
+ final float z = mRadiusInMatrixSpace * v;
+
+ mCamera.translate(0.0f, 0.0f, z);
+
+ mCamera.getMatrix(mTemp);
+ m.postConcat(mTemp);
+
+ mCamera.restore();
+ }
+
+
+ private void addChildScale(View v,Matrix m){
+ final float f = getScaleFactor(getChildsCenter(v));
+ m.postScale(f, f);
+ }
+
+ private void addChildRotation(View v, Matrix m){
+ mCamera.save();
+
+ final int c = getChildsCenter(v);
+ mCamera.rotateY(getRotationAngle(c) - getAngleOnCircle(c));
+
+ mCamera.getMatrix(mTemp);
+ m.postConcat(mTemp);
+
+ mCamera.restore();
+ }
+
+ private void addChildAdjustPosition(View child, Matrix m) {
+ final int c = getChildsCenter(child);
+ final float crp = getClampedRelativePosition(getRelativePosition(c), mAdjustPositionThreshold * getWidgetSizeMultiplier());
+ final float d = mCoverWidth * mAdjustPositionMultiplier * mSpacing * crp * getSpacingMultiplierOnCirlce(c);
+
+ m.postTranslate(d, 0f);
+ }
+
+ /**
+ * Calculates relative position on screen in range -1 to 1, widgets out of screen can have values ove 1 or -1
+ * @param pixexPos Absolute position in pixels including scroll offset
+ * @return relative position
+ */
+ private float getRelativePosition(int pixexPos){
+ final int half = getWidth()/2;
+ final int centerPos = getScrollX() + half;
+
+ return (pixexPos - centerPos)/((float) half);
+ }
+
+ /**
+ * Clamps relative position by threshold, and produces values in range -1 to 1 directly usable for transformation computation
+ * @param position value int range -1 to 1
+ * @param threshold always positive value of threshold distance from center in range 0-1
+ * @return
+ */
+ private float getClampedRelativePosition(float position, float threshold){
+ if(position < 0){
+ if(position < -threshold) return -1f;
+ else return position/threshold;
+ }
+ else{
+ if(position > threshold) return 1;
+ else return position/threshold;
+ }
+ }
+
+ private float getRotationAngle(int childCenter){
+ return -mMaxRotationAngle * getClampedRelativePosition(getRelativePosition(childCenter), mRotationThreshold * getWidgetSizeMultiplier());
+ }
+
+ private float getScaleFactor(int childCenter){
+ return 1 + (mMaxScaleFactor-1) * (1 - Math.abs(getClampedRelativePosition(getRelativePosition(childCenter), mScalingThreshold * getWidgetSizeMultiplier())));
+ }
+
+
+ /**
+ * Compute offset following path on circle
+ * @param childCenter
+ * @return offset from position on unitary circle
+ */
+ private float getOffsetOnCircle(int childCenter){
+ float x = getRelativePosition(childCenter)/mRadius;
+ if(x < -1.0f) x = -1.0f;
+ if(x > 1.0f) x = 1.0f;
+
+ return (float) (1 - Math.sin(Math.acos(x)));
+ }
+
+ private float getAngleOnCircle(int childCenter){
+ float x = getRelativePosition(childCenter)/mRadius;
+ if(x < -1.0f) x = -1.0f;
+ if(x > 1.0f) x = 1.0f;
+
+ return (float) (Math.acos(x)/Math.PI*180.0f - 90.0f);
+ }
+
+ private float getSpacingMultiplierOnCirlce(int childCenter){
+ float x = getRelativePosition(childCenter)/mRadius;
+ return (float) Math.sin(Math.acos(x));
+ }
+
+
+
+ @Override
+ protected void handleClick(Point p) {
+ final int c = getChildCount();
+ View v;
+ final RectF r = new RectF();
+ final int[] childOrder = new int[c];
+
+
+ for(int i=0; i < c; i++){
+ childOrder[i] = getChildDrawingOrder(c, i);
+ }
+
+ for(int i = c-1; i >= 0; i--){
+ v = getChildAt(childOrder[i]); //we need reverse drawing order. Check children drawn last first
+ getScrolledTransformedChildRectangle(v, r);
+ if(r.contains(p.x,p.y)){
+ final View old = getSelectedView();
+ if(old != null) old.setSelected(false);
+
+
+ int position = mFirstItemPosition + childOrder[i];
+ if(position >= mAdapter.getCount()) position = position - mAdapter.getCount();
+
+
+ mSelectedPosition = position;
+ v.setSelected(true);
+
+ if(mOnItemClickListener != null) mOnItemClickListener.onItemClick(this, v, position , getItemIdAtPosition(position));
+ if(mOnItemSelectedListener != null) mOnItemSelectedListener.onItemSelected(this, v, position, getItemIdAtPosition(position));
+
+
+ break;
+ }
+ }
+ }
+
+
+
+ @Override
+ public void computeScroll() {
+ // if we don't have an adapter, we don't need to do anything
+ if (mAdapter == null) {
+ return;
+ }
+ if(mAdapter.getCount() == 0){
+ return;
+ }
+
+ if(getChildCount() == 0){ //release memory resources was probably called before, and onLayout didn't get called to fill container again
+ requestLayout();
+ }
+
+ if (mTouchState == TOUCH_STATE_ALIGN) {
+ if(mAlignScroller.computeScrollOffset()) {
+ if(mAlignScroller.getFinalX() == mAlignScroller.getCurrX()){
+ mAlignScroller.abortAnimation();
+ mTouchState = TOUCH_STATE_RESTING;
+ clearChildrenCache();
+ return;
+ }
+
+ int x = mAlignScroller.getCurrX();
+ scrollTo(x, 0);
+
+ postInvalidate();
+ return;
+ }
+ else{
+ mTouchState = TOUCH_STATE_RESTING;
+ clearChildrenCache();
+ return;
+ }
+ }
+
+ super.computeScroll();
+ }
+
+ @Override
+ protected boolean checkScrollPosition() {
+ if(mCenterItemOffset != 0){
+ mAlignScroller.startScroll(getScrollX(), 0, mCenterItemOffset, 0, mAlignTime);
+ mTouchState = TOUCH_STATE_ALIGN;
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+
+ private void getScrolledTransformedChildRectangle(View child, RectF r){
+ transformChildHitRectangle(child, r);
+ final int offset = child.getLeft() - getScrollX();
+ r.offset(offset, child.getTop());
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ final float xf = ev.getX();
+ final float yf = ev.getY();
+ final RectF frame = mTouchRect;
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ if (mMotionTarget != null) {
+ // this is weird, we got a pen down, but we thought it was
+ // already down!
+ // We should probably send an ACTION_UP to the current
+ // target.
+ mMotionTarget = null;
+ }
+ // If we're disallowing intercept or if we're allowing and we didn't
+ // intercept
+ if (!onInterceptTouchEvent(ev)) {
+ // reset this event's action (just to protect ourselves)
+ ev.setAction(MotionEvent.ACTION_DOWN);
+ // We know we want to dispatch the event down, find a child
+ // who can handle it, start with the front-most child.
+
+ final int count = getChildCount();
+ final int[] childOrder = new int[count];
+
+ for(int i=0; i < count; i++){
+ childOrder[i] = getChildDrawingOrder(count, i);
+ }
+
+ for(int i = count-1; i >= 0; i--) {
+ final View child = getChildAt(childOrder[i]);
+ if (child.getVisibility() == VISIBLE
+ || child.getAnimation() != null) {
+
+ getScrolledTransformedChildRectangle(child, frame);
+
+ if (frame.contains(xf, yf)) {
+ // offset the event to the view's coordinate system
+ final float xc = xf - frame.left;
+ final float yc = yf - frame.top;
+ ev.setLocation(xc, yc);
+ if (child.dispatchTouchEvent(ev)) {
+ // Event handled, we have a target now.
+ mMotionTarget = child;
+ mTargetTop = frame.top;
+ mTargetLeft = frame.left;
+ return true;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
+ (action == MotionEvent.ACTION_CANCEL);
+
+
+ // The event wasn't an ACTION_DOWN, dispatch it to our target if
+ // we have one.
+ final View target = mMotionTarget;
+ if (target == null) {
+ // We don't have a target, this means we're handling the
+ // event as a regular view.
+ ev.setLocation(xf, yf);
+ return onTouchEvent(ev);
+ }
+
+ // if have a target, see if we're allowed to and want to intercept its
+ // events
+ if (onInterceptTouchEvent(ev)) {
+ final float xc = xf - mTargetLeft;
+ final float yc = yf - mTargetTop;
+ ev.setAction(MotionEvent.ACTION_CANCEL);
+ ev.setLocation(xc, yc);
+ if (!target.dispatchTouchEvent(ev)) {
+ // target didn't handle ACTION_CANCEL. not much we can do
+ // but they should have.
+ }
+ // clear the target
+ mMotionTarget = null;
+ // Don't dispatch this event to our own view, because we already
+ // saw it when intercepting; we just want to give the following
+ // event to the normal onTouchEvent().
+ return true;
+ }
+
+ if (isUpOrCancel) {
+ mMotionTarget = null;
+ mTargetTop = -1;
+ mTargetLeft = -1;
+ }
+
+ // finally offset the event to the target's coordinate system and
+ // dispatch the event.
+ final float xc = xf - mTargetLeft;
+ final float yc = yf - mTargetTop;
+ ev.setLocation(xc, yc);
+
+ return target.dispatchTouchEvent(ev);
+ }
+
+
+ @SuppressWarnings("deprecation")
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+
+ int h,w;
+ if(heightSpecMode == MeasureSpec.EXACTLY) h = heightSpecSize;
+ else{
+ h = (int) ((mCoverHeight + mCoverHeight*mReflectionHeight + mReflectionGap) * mMaxScaleFactor + mPaddingTop + mPaddingBottom);
+ h = resolveSize(h, heightMeasureSpec);
+ }
+
+ if(widthSpecMode == MeasureSpec.EXACTLY) w = widthSpecSize;
+ else{
+ WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ w = display.getWidth();
+ w = resolveSize(w, widthMeasureSpec);
+ }
+
+ setMeasuredDimension(w, h);
+ }
+
+
+ //disable turning caches of and on, we need them always on
+ @Override
+ protected void enableChildrenCache() {}
+
+ @Override
+ protected void clearChildrenCache() {}
+
+ /**
+ * How many items can remain in cache. Lower in case of memory issues
+ * @param size number of cached covers
+ */
+ public void trimChacheSize(int size){
+ mCachedFrames.trimToSize(size);
+ }
+
+ /**
+ * Clear internal cover cache
+ */
+ public void clearCache(){
+ mCachedFrames.evictAll();
+ }
+
+ /**
+ * Returns widget spacing (as fraction of widget size)
+ * @return Widgets spacing
+ */
+ public float getSpacing() {
+ return mSpacing;
+ }
+
+ /**
+ * Set widget spacing (float means fraction of widget size, 1 = widget size)
+ * @param spacing the spacing to set
+ */
+ public void setSpacing(float spacing) {
+ this.mSpacing = spacing;
+ }
+
+ /**
+ * Return width of cover in pixels
+ * @return the Cover Width
+ */
+ public int getCoverWidth() {
+ return mCoverWidth;
+ }
+
+ /**
+ * Set width of cover in pixels
+ * @param coverWidth the Cover Width to set
+ */
+ public void setCoverWidth(int coverWidth) {
+ if(coverWidth % 2 == 1) coverWidth--;
+ this.mCoverWidth = coverWidth;
+ }
+
+ /**
+ * Return cover height in pixels
+ * @return the Cover Height
+ */
+ public int getCoverHeight() {
+ return mCoverHeight;
+ }
+
+ /**
+ * Set cover height in pixels
+ * @param coverHeight the Cover Height to set
+ */
+ public void setCoverHeight(int coverHeight) {
+ this.mCoverHeight = coverHeight;
+ }
+
+ /**
+ * Sets distance from center as fraction of half of widget size where covers start to rotate into center
+ * 1 means rotation starts on edge of widget, 0 means only center rotated
+ * @param rotationThreshold the rotation threshold to set
+ */
+ public void setRotationTreshold(float rotationThreshold) {
+ this.mRotationThreshold = rotationThreshold;
+ }
+
+ /**
+ * Sets distance from center as fraction of half of widget size where covers start to zoom in
+ * 1 means scaling starts on edge of widget, 0 means only center scaled
+ * @param scalingThreshold the scaling threshold to set
+ */
+ public void setScalingThreshold(float scalingThreshold) {
+ this.mScalingThreshold = scalingThreshold;
+ }
+
+ /**
+ * Sets distance from center as fraction of half of widget size,
+ * where covers start enlarge their spacing to allow for smooth passing each other without jumping over each other
+ * 1 means edge of widget, 0 means only center
+ * @param adjustPositionThreshold the adjust position threshold to set
+ */
+ public void setAdjustPositionThreshold(float adjustPositionThreshold) {
+ this.mAdjustPositionThreshold = adjustPositionThreshold;
+ }
+
+ /**
+ * Sets adjust position multiplier. By enlarging this value, you can enlarge spacing in center of widget done by position adjustment
+ * @param adjustPositionMultiplier the adjust position multiplier to set
+ */
+ public void setAdjustPositionMultiplier(float adjustPositionMultiplier) {
+ this.mAdjustPositionMultiplier = adjustPositionMultiplier;
+ }
+
+ /**
+ * Sets absolute value of rotation angle of cover at edge of widget in degrees.
+ * Rotation made by traveling around circle path is added to this value separately.
+ * By enlarging this value you make covers more rotated. Max value without traveling on circle would be 90 degrees.
+ * With small circle radius could go even over this value sometimes. Look depends also on other parameters.
+ * @param maxRotationAngle the max rotation angle to set
+ */
+ public void setMaxRotationAngle(float maxRotationAngle) {
+ this.mMaxRotationAngle = maxRotationAngle;
+ }
+
+ /**
+ * Sets scale factor of item in center. Normal size is multiplied with this value
+ * @param maxScaleFactor the max scale factor to set
+ */
+ public void setMaxScaleFactor(float maxScaleFactor) {
+ this.mMaxScaleFactor = maxScaleFactor;
+ }
+
+ /**
+ * Sets radius of circle path which covers follow. Range of screen is -1 to 1, minimal radius is therefore 1
+ * This value affect how big part of circle path you see on screen and therefore how much away are covers at edge of screen.
+ * And also how much they are rotated in direction of circle path.
+ * @param radius the radius to set
+ */
+ public void setRadius(float radius) {
+ this.mRadius = radius;
+ }
+
+ /**
+ * This value affects how far are covers at the edges of widget in Z coordinate in matrix space
+ * @param radiusInMatrixSpace the radius in matrix space to set
+ */
+ public void setRadiusInMatrixSpace(float radiusInMatrixSpace) {
+ this.mRadiusInMatrixSpace = radiusInMatrixSpace;
+ }
+
+ /**
+ * Reflection height as a fraction of cover height (1 means same size as original)
+ * @param reflectionHeight the reflection height to set
+ */
+ public void setReflectionHeight(float reflectionHeight) {
+ this.mReflectionHeight = reflectionHeight;
+ }
+
+ /**
+ * @param reflectionGap Gap between original image and reflection in pixels
+ */
+ public void setReflectionGap(int reflectionGap) {
+ this.mReflectionGap = reflectionGap;
+ }
+
+ /**
+ * @param reflectionOpacity Opacity at most opaque part of reflection fade out effect
+ */
+ public void setReflectionOpacity(int reflectionOpacity) {
+ this.mReflectionOpacity = reflectionOpacity;
+ }
+
+ /**
+ * Widget size on which was tuning of parameters done. This value is used to scale parameters when widgets has different size
+ * @param size returned by widgets getWidth()
+ */
+ public void setTuningWidgetSize(int size) {
+ this.mTuningWidgetSize = size;
+ }
+
+ /**
+ * @param alignTime How long takes center alignment animation in milliseconds
+ */
+ public void setAlignTime(int alignTime) {
+ this.mAlignTime = alignTime;
+ }
+
+ /**
+ * @param paddingTop
+ */
+ public void setVerticalPaddingTop(int paddingTop) {
+ this.mPaddingTop = paddingTop;
+ }
+
+ public void setVerticalPaddingBottom(int paddingBottom) {
+ this.mPaddingBottom = paddingBottom;
+ }
+
+
+ /**
+ * Set this to some color if you don't want see through reflections other reflections. Preferably set to same color as background color
+ * @param reflectionBackgroundColor the Reflection Background Color to set
+ */
+ public void setReflectionBackgroundColor(int reflectionBackgroundColor) {
+ this.mReflectionBackgroundColor = reflectionBackgroundColor;
+ }
+
+ @Override
+ /**
+ * Get position of center item in adapter.
+ * @return position of center item inside adapter date or -1 if there is no center item shown
+ */
+ public int getScrollPosition() {
+ if(mAdapter == null || mAdapter.getCount() == 0) return -1;
+
+ if(mLastCenterItemIndex != -1){
+ return (mFirstItemPosition + mLastCenterItemIndex) % mAdapter.getCount();
+ }
+ else return (mFirstItemPosition + (getWidth()/((int)(mCoverWidth * mSpacing)))/2) % mAdapter.getCount();
+ }
+
+ /**
+ * Set new center item position
+ */
+ @Override
+ public void scrollToPosition(int position) {
+ if(mAdapter == null || mAdapter.getCount() == 0) throw new IllegalStateException("You are trying to scroll container with no adapter set. Set adapter first.");
+
+ if(mLastCenterItemIndex != -1){
+ final int lastCenterItemPosition = (mFirstItemPosition + mLastCenterItemIndex) % mAdapter.getCount();
+ final int di = lastCenterItemPosition - position;
+ final int dst = (int) (di * mCoverWidth * mSpacing);
+ mScrollToPositionOnNextInvalidate = -1;
+ scrollBy(-dst, 0);
+ }
+ else{
+ mScrollToPositionOnNextInvalidate = position;
+ }
+
+ invalidate();
+ }
+
+ /**
+ * sets listener for center item position
+ * @param onScrollPositionListener
+ */
+ public void setOnScrollPositionListener(OnScrollPositionListener onScrollPositionListener){
+ mOnScrollPositionListener = onScrollPositionListener;
+ }
+
+ /**
+ * removes children, must be after caching children
+ * @param cf
+ */
+ private void recycleCoverFrame(CoverFrame cf){
+ cf.recycle();
+ WeakReference ref = new WeakReference(cf);
+ mRecycledCoverFrames.addLast(ref);
+ }
+
+ protected CoverFrame getRecycledCoverFrame(){
+ if (!mRecycledCoverFrames.isEmpty()) {
+ CoverFrame v;
+ do{
+ v = mRecycledCoverFrames.removeFirst().get();
+ }
+ while(v == null && !mRecycledCoverFrames.isEmpty());
+ return v;
+ }
+ return null;
+ }
+
+ /**
+ * Removes links to all pictures which are hold by coverflow to speed up rendering
+ * Sets environment to state from which it can be refilled on next onLayout
+ * Good place to release resources is in activitys onStop.
+ */
+ public void releaseAllMemoryResources(){
+ mLastItemPosition = mFirstItemPosition;
+ mLastItemPosition--;
+
+ final int w = (int)(mCoverWidth*mSpacing);
+ int sp = getScrollX() % w;
+ if(sp < 0) sp = sp + w;
+ scrollTo(sp, 0);
+
+ removeAllViewsInLayout();
+ clearCache();
+ }
+
+ @Override
+ public boolean onPreDraw() { //when child view is about to be drawn we invalidate whole container
+
+ if(!mInvalidated){ //this is hack, no idea now is possible that this works, but fixes problem where not all area was redrawn
+ mInvalidated = true;
+ invalidate();
+ return false;
+ }
+
+ return true;
+
+ }
+
+
+
+}
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/HorizontalList.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/HorizontalList.java
similarity index 95%
rename from MAComponents/src/com/martinappl/components/ui/containers/HorizontalList.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/HorizontalList.java
index acecac0..19466f8 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/HorizontalList.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/HorizontalList.java
@@ -1,664 +1,665 @@
-package com.martinappl.components.ui.containers;
-
-
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.Adapter;
-import android.widget.Scroller;
-
-import com.martinappl.components.general.ToolBox;
-import com.martinappl.components.ui.containers.interfaces.IViewObserver;
-
-public class HorizontalList extends ViewGroup {
- protected final int NO_VALUE = -11;
-
- /** User is not touching the list */
- protected static final int TOUCH_STATE_RESTING = 0;
-
- /** User is scrolling the list */
- protected static final int TOUCH_STATE_SCROLLING = 1;
-
- /** Fling gesture in progress */
- protected static final int TOUCH_STATE_FLING = 2;
-
- /** Children added with this layout mode will be added after the last child */
- protected static final int LAYOUT_MODE_AFTER = 0;
-
- /** Children added with this layout mode will be added before the first child */
- protected static final int LAYOUT_MODE_TO_BEFORE = 1;
-
- protected int mFirstItemPosition;
- protected int mLastItemPosition;
- protected boolean isScrollingDisabled = false;
-
- protected Adapter mAdapter;
- protected final ToolBox.ViewCache mCache = new ToolBox.ViewCache();
- private final Scroller mScroller = new Scroller(getContext());
- protected int mTouchSlop;
- private int mMinimumVelocity;
- private int mMaximumVelocity;
-
- private int mTouchState = TOUCH_STATE_RESTING;
- private float mLastMotionX;
- private final Point mDown = new Point();
- private VelocityTracker mVelocityTracker;
- private boolean mHandleSelectionOnActionUp = false;
-
- protected int mRightEdge = NO_VALUE;
- private int mDefaultItemWidth = 200;
-
- protected IViewObserver mViewObserver;
-
- //listeners
- private OnItemClickListener mItemClickListener;
-
- private final DataSetObserver mDataObserver = new DataSetObserver() {
-
- @Override
- public void onChanged() {
- reset();
- invalidate();
- }
-
- @Override
- public void onInvalidated() {
- removeAllViews();
- invalidate();
- }
-
- };
-
- /**
- * Remove all data, reset to initial state and attempt to refill
- * Position of first item on screen in Adapter data set is maintained
- */
- private void reset() {
- int scroll = getScrollX();
-
- int left = 0;
- if(getChildCount() != 0){
- left = getChildAt(0).getLeft() - ((MarginLayoutParams)getChildAt(0).getLayoutParams()).leftMargin;
- }
-
- removeAllViewsInLayout();
- mLastItemPosition = mFirstItemPosition;
- mRightEdge = NO_VALUE;
- scrollTo(left, 0);
-
- final int leftScreenEdge = getScrollX();
- int rightScreenEdge = leftScreenEdge + getWidth();
-
- refillLeftToRight(leftScreenEdge, rightScreenEdge);
- refillRightToLeft(leftScreenEdge);
-
- scrollTo(scroll, 0);
- }
-
- public HorizontalList(Context context) {
- this(context, null);
- }
-
- public HorizontalList(Context context, AttributeSet attrs) {
- this(context, attrs,0);
- }
-
- public HorizontalList(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- final ViewConfiguration configuration = ViewConfiguration.get(context);
- mTouchSlop = configuration.getScaledTouchSlop();
- mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
- mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
- }
-
- public interface OnItemClickListener{
- void onItemClick(View v);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- refill();
- }
-
- /**
- * Checks and refills empty area on the left
- * @return firstItemPosition
- */
- protected void refillRightToLeft(final int leftScreenEdge){
- if(getChildCount() == 0) return;
-
- View child = getChildAt(0);
- int childLeft = child.getLeft();
- int lastLeft = childLeft - ((MarginLayoutParams)child.getLayoutParams()).leftMargin;
-
- while(lastLeft > leftScreenEdge && mFirstItemPosition > 0){
- mFirstItemPosition--;
-
- child = mAdapter.getView(mFirstItemPosition, mCache.getCachedView(), this);
- sanitizeLayoutParams(child);
-
- addAndMeasureChild(child, LAYOUT_MODE_TO_BEFORE);
- final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
- lastLeft = layoutChildToBefore(child, lastLeft, lp);
- childLeft = child.getLeft() - ((MarginLayoutParams)child.getLayoutParams()).leftMargin;
-
- }
- return;
- }
-
- /**
- * Checks and refills empty area on the right
- */
- protected void refillLeftToRight(final int leftScreenEdge, final int rightScreenEdge){
-
- View child;
- int lastRight;
- if(getChildCount() != 0){
- child = getChildAt(getChildCount() - 1);
- lastRight = child.getRight() + ((MarginLayoutParams)child.getLayoutParams()).rightMargin;
- }
- else{
- lastRight = leftScreenEdge;
- if(mLastItemPosition == mFirstItemPosition) mLastItemPosition--;
- }
-
- while(lastRight < rightScreenEdge && mLastItemPosition < mAdapter.getCount()-1){
- mLastItemPosition++;
-
- child = mAdapter.getView(mLastItemPosition, mCache.getCachedView(), this);
- sanitizeLayoutParams(child);
-
- addAndMeasureChild(child, LAYOUT_MODE_AFTER);
- final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
- lastRight = layoutChild(child, lastRight, lp);
-
- if(mLastItemPosition >= mAdapter.getCount()-1) {
- mRightEdge = lastRight;
- }
- }
- }
-
-
- /**
- * Remove non visible views from left edge of screen
- */
- protected void removeNonVisibleViewsLeftToRight(final int leftScreenEdge){
- if(getChildCount() == 0) return;
-
- // check if we should remove any views in the left
- View firstChild = getChildAt(0);
-
- while (firstChild != null && firstChild.getRight() + ((MarginLayoutParams)firstChild.getLayoutParams()).rightMargin < leftScreenEdge) {
-
- // remove view
- removeViewsInLayout(0, 1);
-
- if(mViewObserver != null) mViewObserver.onViewRemovedFromParent(firstChild, mFirstItemPosition);
- mCache.cacheView(firstChild);
-
- mFirstItemPosition++;
- if(mFirstItemPosition >= mAdapter.getCount()) mFirstItemPosition = 0;
-
- // Continue to check the next child only if we have more than
- // one child left
- if (getChildCount() > 1) {
- firstChild = getChildAt(0);
- } else {
- firstChild = null;
- }
- }
-
- }
-
- /**
- * Remove non visible views from right edge of screen
- */
- protected void removeNonVisibleViewsRightToLeft(final int rightScreenEdge){
- if(getChildCount() == 0) return;
-
- // check if we should remove any views in the right
- View lastChild = getChildAt(getChildCount() - 1);
- while (lastChild != null && lastChild.getLeft() - ((MarginLayoutParams)lastChild.getLayoutParams()).leftMargin > rightScreenEdge) {
- // remove the right view
- removeViewsInLayout(getChildCount() - 1, 1);
-
- if(mViewObserver != null) mViewObserver.onViewRemovedFromParent(lastChild, mLastItemPosition);
- mCache.cacheView(lastChild);
-
- mLastItemPosition--;
- if(mLastItemPosition < 0) mLastItemPosition = mAdapter.getCount()-1;
-
- // Continue to check the next child only if we have more than
- // one child left
- if (getChildCount() > 1) {
- lastChild = getChildAt(getChildCount() - 1);
- } else {
- lastChild = null;
- }
- }
-
- }
-
- protected void refill(){
- if(mAdapter == null) return;
-
- final int leftScreenEdge = getScrollX();
- int rightScreenEdge = leftScreenEdge + getWidth();
-
- removeNonVisibleViewsLeftToRight(leftScreenEdge);
- removeNonVisibleViewsRightToLeft(rightScreenEdge);
-
- refillLeftToRight(leftScreenEdge, rightScreenEdge);
- refillRightToLeft(leftScreenEdge);
- }
-
-
-
- protected void sanitizeLayoutParams(View child){
- MarginLayoutParams lp;
- if(child.getLayoutParams() instanceof MarginLayoutParams) lp = (MarginLayoutParams) child.getLayoutParams();
- else if(child.getLayoutParams() != null) lp = new MarginLayoutParams(child.getLayoutParams());
- else lp = new MarginLayoutParams(mDefaultItemWidth,getHeight());
-
- if(lp.height == LayoutParams.MATCH_PARENT) lp.height = getHeight();
- if(lp.width == LayoutParams.MATCH_PARENT) lp.width = getWidth();
-
- if(lp.height == LayoutParams.WRAP_CONTENT){
- measureUnspecified(child);
- lp.height = child.getMeasuredHeight();
- }
- if(lp.width == LayoutParams.WRAP_CONTENT){
- measureUnspecified(child);
- lp.width = child.getMeasuredWidth();
- }
- child.setLayoutParams(lp);
- }
-
- private void measureUnspecified(View child){
- final int pwms = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.UNSPECIFIED);
- final int phms = MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.UNSPECIFIED);
- measureChild(child, pwms, phms);
- }
-
- /**
- * Adds a view as a child view and takes care of measuring it
- *
- * @param child The view to add
- * @param layoutMode Either LAYOUT_MODE_LEFT or LAYOUT_MODE_RIGHT
- * @return child which was actually added to container, subclasses can override to introduce frame views
- */
- protected View addAndMeasureChild(final View child, final int layoutMode) {
- if(child.getLayoutParams() == null) child.setLayoutParams(new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
-
- final int index = layoutMode == LAYOUT_MODE_TO_BEFORE ? 0 : -1;
- addViewInLayout(child, index, child.getLayoutParams(), true);
-
- final int pwms = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
- final int phms = MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY);
- measureChild(child, pwms, phms);
- child.setDrawingCacheEnabled(false);
-
- return child;
- }
-
- /**
- * Layout children from right to left
- */
- protected int layoutChildToBefore(View v, int right , MarginLayoutParams lp){
- final int left = right - v.getMeasuredWidth() - lp.leftMargin - lp.rightMargin;
- layoutChild(v, left, lp);
- return left;
- }
-
- /**
- * @param topline Y coordinate of topline
- * @param left X coordinate where should we start layout
- */
- protected int layoutChild(View v, int left, MarginLayoutParams lp){
- int l,t,r,b;
- l = left + lp.leftMargin;
- t = lp.topMargin;
- r = l + v.getMeasuredWidth();
- b = t + v.getMeasuredHeight();
-
- v.layout(l, t, r, b);
- return r + lp.rightMargin;
- }
-
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
-
- /*
- * This method JUST determines whether we want to intercept the motion.
- * If we return true, onTouchEvent will be called and we do the actual
- * scrolling there.
- */
-
-
- /*
- * Shortcut the most recurring case: the user is in the dragging
- * state and he is moving his finger. We want to intercept this
- * motion.
- */
- final int action = ev.getAction();
- if ((action == MotionEvent.ACTION_MOVE) && (mTouchState == TOUCH_STATE_SCROLLING)) {
- return true;
- }
-
- final float x = ev.getX();
- final float y = ev.getY();
- switch (action) {
- case MotionEvent.ACTION_MOVE:
- /*
- * not dragging, otherwise the shortcut would have caught it. Check
- * whether the user has moved far enough from his original down touch.
- */
-
- /*
- * Locally do absolute value. mLastMotionX is set to the x value
- * of the down event.
- */
- final int xDiff = (int) Math.abs(x - mLastMotionX);
-
- final int touchSlop = mTouchSlop;
- final boolean xMoved = xDiff > touchSlop;
-
- if (xMoved) {
- // Scroll if the user moved far enough along the axis
- mTouchState = TOUCH_STATE_SCROLLING;
- mHandleSelectionOnActionUp = false;
- enableChildrenCache();
- cancelLongPress();
- }
-
- break;
-
- case MotionEvent.ACTION_DOWN:
- // Remember location of down touch
- mLastMotionX = x;
-
- mDown.x = (int) x;
- mDown.y = (int) y;
-
- /*
- * If being flinged and user touches the screen, initiate drag;
- * otherwise don't. mScroller.isFinished should be false when
- * being flinged.
- */
- mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTING : TOUCH_STATE_SCROLLING;
- //if he had normal click in rested state, remember for action up check
- if(mTouchState == TOUCH_STATE_RESTING){
- mHandleSelectionOnActionUp = true;
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- mDown.x = -1;
- mDown.y = -1;
- break;
- case MotionEvent.ACTION_UP:
- //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
- if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
- final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
- if((ev.getEventTime() - ev.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
- }
- // Release the drag
- mHandleSelectionOnActionUp = false;
- mDown.x = -1;
- mDown.y = -1;
-
- mTouchState = TOUCH_STATE_RESTING;
- clearChildrenCache();
- break;
- }
-
- return mTouchState == TOUCH_STATE_SCROLLING;
-
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
-
- final int action = event.getAction();
- final float x = event.getX();
- final float y = event.getY();
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- /*
- * If being flinged and user touches, stop the fling. isFinished
- * will be false if being flinged.
- */
- if (!mScroller.isFinished()) {
- mScroller.forceFinished(true);
- }
-
- // Remember where the motion event started
- mLastMotionX = x;
-
- break;
- case MotionEvent.ACTION_MOVE:
-
- if (mTouchState == TOUCH_STATE_SCROLLING) {
- // Scroll to follow the motion event
- final int deltaX = (int) (mLastMotionX - x);
- mLastMotionX = x;
-
- scrollByDelta(deltaX);
- }
- else{
- final int xDiff = (int) Math.abs(x - mLastMotionX);
-
- final int touchSlop = mTouchSlop;
- final boolean xMoved = xDiff > touchSlop;
-
-
- if (xMoved) {
- // Scroll if the user moved far enough along the axis
- mTouchState = TOUCH_STATE_SCROLLING;
- enableChildrenCache();
- cancelLongPress();
- }
- }
- break;
- case MotionEvent.ACTION_UP:
-
- //this must be here, in case no child view returns true,
- //events will propagate back here and on intercept touch event wont be called again
- //in case of no parent it propagates here, in case of parent it usually propagates to on cancel
- if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
- final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
- if((event.getEventTime() - event.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
- mHandleSelectionOnActionUp = false;
- }
-
- //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
- if (mTouchState == TOUCH_STATE_SCROLLING) {
-
- mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialXVelocity = (int) mVelocityTracker.getXVelocity();
- int initialYVelocity = (int) mVelocityTracker.getYVelocity();
-
- if (Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) {
- fling(-initialXVelocity, -initialYVelocity);
- }
- else{
- // Release the drag
- clearChildrenCache();
- mTouchState = TOUCH_STATE_RESTING;
-
- mDown.x = -1;
- mDown.y = -1;
- }
-
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
-
- break;
- }
-
- // Release the drag
- clearChildrenCache();
- mTouchState = TOUCH_STATE_RESTING;
-
- mDown.x = -1;
- mDown.y = -1;
-
- break;
- case MotionEvent.ACTION_CANCEL:
- mTouchState = TOUCH_STATE_RESTING;
- }
-
- return true;
- }
-
- @Override
- public void computeScroll() {
- if(mRightEdge != NO_VALUE && mScroller.getFinalX() > mRightEdge - getWidth() + 1){
- mScroller.setFinalX(mRightEdge - getWidth() + 1);
- }
-
- if(mRightEdge != NO_VALUE && getScrollX() > mRightEdge - getWidth()) {
- if(mRightEdge - getWidth() > 0) scrollTo(mRightEdge - getWidth(), 0);
- else scrollTo(0, 0);
- return;
- }
-
- if (mScroller.computeScrollOffset()) {
- if(mScroller.getFinalX() == mScroller.getCurrX()){
- mScroller.abortAnimation();
- mTouchState = TOUCH_STATE_RESTING;
- clearChildrenCache();
- }
- else{
- final int x = mScroller.getCurrX();
- scrollTo(x, 0);
-
- postInvalidate();
- }
- }
- else if(mTouchState == TOUCH_STATE_FLING){
- mTouchState = TOUCH_STATE_RESTING;
- clearChildrenCache();
- }
-
- refill();
- }
-
- public void fling(int velocityX, int velocityY){
- if(isScrollingDisabled) return;
-
- mTouchState = TOUCH_STATE_FLING;
- final int x = getScrollX();
- final int y = getScrollY();
-
- final int rightInPixels;
- if(mRightEdge == NO_VALUE) rightInPixels = Integer.MAX_VALUE;
- else rightInPixels = mRightEdge;
-
- mScroller.fling(x, y, velocityX, velocityY, 0,rightInPixels - getWidth() + 1,0,0);
-
- invalidate();
- }
-
- protected void scrollByDelta(int deltaX){
- if(isScrollingDisabled) return;
-
- final int rightInPixels;
- if(mRightEdge == NO_VALUE) rightInPixels = Integer.MAX_VALUE;
- else {
- rightInPixels = mRightEdge;
- if(getScrollX() > mRightEdge - getWidth()) {
- if(mRightEdge - getWidth() > 0) scrollTo(mRightEdge - getWidth(), 0);
- else scrollTo(0, 0);
- return;
- }
- }
-
- final int x = getScrollX() + deltaX;
-
- if(x < 0 ) deltaX -= x;
- else if(x > rightInPixels - getWidth()) deltaX -= x - (rightInPixels - getWidth());
-
- scrollBy(deltaX, 0);
- }
-
- protected void handleClick(Point p){
- final int c = getChildCount();
- View v;
- final Rect r = new Rect();
- for(int i=0; i < c; i++){
- v = getChildAt(i);
- v.getHitRect(r);
- if(r.contains(getScrollX() + p.x, getScrollY() + p.y)){
- if(mItemClickListener != null) mItemClickListener.onItemClick(v);
- }
- }
- }
-
-
- public void setAdapter(Adapter adapter) {
- if(mAdapter != null) {
- mAdapter.unregisterDataSetObserver(mDataObserver);
- }
- mAdapter = adapter;
- mAdapter.registerDataSetObserver(mDataObserver);
- reset();
- }
-
- private void enableChildrenCache() {
- setChildrenDrawnWithCacheEnabled(true);
- setChildrenDrawingCacheEnabled(true);
- }
-
- private void clearChildrenCache() {
- setChildrenDrawnWithCacheEnabled(false);
- }
-
- @Override
- protected MarginLayoutParams generateDefaultLayoutParams() {
- return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
- }
-
- @Override
- protected MarginLayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new MarginLayoutParams(p);
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof MarginLayoutParams;
- }
-
- public void setDefaultItemWidth(int width){
- //MTODO add xml attributes
- mDefaultItemWidth = width;
- }
-
- /**
- * Set listener which will fire if item in container is clicked
- */
- public void setOnItemClickListener(OnItemClickListener itemClickListener) {
- this.mItemClickListener = itemClickListener;
- }
-
- public void setViewObserver(IViewObserver viewObserver) {
- this.mViewObserver = viewObserver;
- }
-
-}
+package it.moondroid.coverflow.components.ui.containers;
+
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.Adapter;
+import android.widget.Scroller;
+
+import it.moondroid.coverflow.components.general.ToolBox;
+import it.moondroid.coverflow.components.ui.containers.interfaces.IViewObserver;
+
+
+public class HorizontalList extends ViewGroup {
+ protected final int NO_VALUE = -11;
+
+ /** User is not touching the list */
+ protected static final int TOUCH_STATE_RESTING = 0;
+
+ /** User is scrolling the list */
+ protected static final int TOUCH_STATE_SCROLLING = 1;
+
+ /** Fling gesture in progress */
+ protected static final int TOUCH_STATE_FLING = 2;
+
+ /** Children added with this layout mode will be added after the last child */
+ protected static final int LAYOUT_MODE_AFTER = 0;
+
+ /** Children added with this layout mode will be added before the first child */
+ protected static final int LAYOUT_MODE_TO_BEFORE = 1;
+
+ protected int mFirstItemPosition;
+ protected int mLastItemPosition;
+ protected boolean isScrollingDisabled = false;
+
+ protected Adapter mAdapter;
+ protected final ToolBox.ViewCache mCache = new ToolBox.ViewCache();
+ private final Scroller mScroller = new Scroller(getContext());
+ protected int mTouchSlop;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
+
+ private int mTouchState = TOUCH_STATE_RESTING;
+ private float mLastMotionX;
+ private final Point mDown = new Point();
+ private VelocityTracker mVelocityTracker;
+ private boolean mHandleSelectionOnActionUp = false;
+
+ protected int mRightEdge = NO_VALUE;
+ private int mDefaultItemWidth = 200;
+
+ protected IViewObserver mViewObserver;
+
+ //listeners
+ private OnItemClickListener mItemClickListener;
+
+ private final DataSetObserver mDataObserver = new DataSetObserver() {
+
+ @Override
+ public void onChanged() {
+ reset();
+ invalidate();
+ }
+
+ @Override
+ public void onInvalidated() {
+ removeAllViews();
+ invalidate();
+ }
+
+ };
+
+ /**
+ * Remove all data, reset to initial state and attempt to refill
+ * Position of first item on screen in Adapter data set is maintained
+ */
+ private void reset() {
+ int scroll = getScrollX();
+
+ int left = 0;
+ if(getChildCount() != 0){
+ left = getChildAt(0).getLeft() - ((MarginLayoutParams)getChildAt(0).getLayoutParams()).leftMargin;
+ }
+
+ removeAllViewsInLayout();
+ mLastItemPosition = mFirstItemPosition;
+ mRightEdge = NO_VALUE;
+ scrollTo(left, 0);
+
+ final int leftScreenEdge = getScrollX();
+ int rightScreenEdge = leftScreenEdge + getWidth();
+
+ refillLeftToRight(leftScreenEdge, rightScreenEdge);
+ refillRightToLeft(leftScreenEdge);
+
+ scrollTo(scroll, 0);
+ }
+
+ public HorizontalList(Context context) {
+ this(context, null);
+ }
+
+ public HorizontalList(Context context, AttributeSet attrs) {
+ this(context, attrs,0);
+ }
+
+ public HorizontalList(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ }
+
+ public interface OnItemClickListener{
+ void onItemClick(View v);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ refill();
+ }
+
+ /**
+ * Checks and refills empty area on the left
+ * @return firstItemPosition
+ */
+ protected void refillRightToLeft(final int leftScreenEdge){
+ if(getChildCount() == 0) return;
+
+ View child = getChildAt(0);
+ int childLeft = child.getLeft();
+ int lastLeft = childLeft - ((MarginLayoutParams)child.getLayoutParams()).leftMargin;
+
+ while(lastLeft > leftScreenEdge && mFirstItemPosition > 0){
+ mFirstItemPosition--;
+
+ child = mAdapter.getView(mFirstItemPosition, mCache.getCachedView(), this);
+ sanitizeLayoutParams(child);
+
+ addAndMeasureChild(child, LAYOUT_MODE_TO_BEFORE);
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ lastLeft = layoutChildToBefore(child, lastLeft, lp);
+ childLeft = child.getLeft() - ((MarginLayoutParams)child.getLayoutParams()).leftMargin;
+
+ }
+ return;
+ }
+
+ /**
+ * Checks and refills empty area on the right
+ */
+ protected void refillLeftToRight(final int leftScreenEdge, final int rightScreenEdge){
+
+ View child;
+ int lastRight;
+ if(getChildCount() != 0){
+ child = getChildAt(getChildCount() - 1);
+ lastRight = child.getRight() + ((MarginLayoutParams)child.getLayoutParams()).rightMargin;
+ }
+ else{
+ lastRight = leftScreenEdge;
+ if(mLastItemPosition == mFirstItemPosition) mLastItemPosition--;
+ }
+
+ while(lastRight < rightScreenEdge && mLastItemPosition < mAdapter.getCount()-1){
+ mLastItemPosition++;
+
+ child = mAdapter.getView(mLastItemPosition, mCache.getCachedView(), this);
+ sanitizeLayoutParams(child);
+
+ addAndMeasureChild(child, LAYOUT_MODE_AFTER);
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ lastRight = layoutChild(child, lastRight, lp);
+
+ if(mLastItemPosition >= mAdapter.getCount()-1) {
+ mRightEdge = lastRight;
+ }
+ }
+ }
+
+
+ /**
+ * Remove non visible views from left edge of screen
+ */
+ protected void removeNonVisibleViewsLeftToRight(final int leftScreenEdge){
+ if(getChildCount() == 0) return;
+
+ // check if we should remove any views in the left
+ View firstChild = getChildAt(0);
+
+ while (firstChild != null && firstChild.getRight() + ((MarginLayoutParams)firstChild.getLayoutParams()).rightMargin < leftScreenEdge) {
+
+ // remove view
+ removeViewsInLayout(0, 1);
+
+ if(mViewObserver != null) mViewObserver.onViewRemovedFromParent(firstChild, mFirstItemPosition);
+ mCache.cacheView(firstChild);
+
+ mFirstItemPosition++;
+ if(mFirstItemPosition >= mAdapter.getCount()) mFirstItemPosition = 0;
+
+ // Continue to check the next child only if we have more than
+ // one child left
+ if (getChildCount() > 1) {
+ firstChild = getChildAt(0);
+ } else {
+ firstChild = null;
+ }
+ }
+
+ }
+
+ /**
+ * Remove non visible views from right edge of screen
+ */
+ protected void removeNonVisibleViewsRightToLeft(final int rightScreenEdge){
+ if(getChildCount() == 0) return;
+
+ // check if we should remove any views in the right
+ View lastChild = getChildAt(getChildCount() - 1);
+ while (lastChild != null && lastChild.getLeft() - ((MarginLayoutParams)lastChild.getLayoutParams()).leftMargin > rightScreenEdge) {
+ // remove the right view
+ removeViewsInLayout(getChildCount() - 1, 1);
+
+ if(mViewObserver != null) mViewObserver.onViewRemovedFromParent(lastChild, mLastItemPosition);
+ mCache.cacheView(lastChild);
+
+ mLastItemPosition--;
+ if(mLastItemPosition < 0) mLastItemPosition = mAdapter.getCount()-1;
+
+ // Continue to check the next child only if we have more than
+ // one child left
+ if (getChildCount() > 1) {
+ lastChild = getChildAt(getChildCount() - 1);
+ } else {
+ lastChild = null;
+ }
+ }
+
+ }
+
+ protected void refill(){
+ if(mAdapter == null) return;
+
+ final int leftScreenEdge = getScrollX();
+ int rightScreenEdge = leftScreenEdge + getWidth();
+
+ removeNonVisibleViewsLeftToRight(leftScreenEdge);
+ removeNonVisibleViewsRightToLeft(rightScreenEdge);
+
+ refillLeftToRight(leftScreenEdge, rightScreenEdge);
+ refillRightToLeft(leftScreenEdge);
+ }
+
+
+
+ protected void sanitizeLayoutParams(View child){
+ MarginLayoutParams lp;
+ if(child.getLayoutParams() instanceof MarginLayoutParams) lp = (MarginLayoutParams) child.getLayoutParams();
+ else if(child.getLayoutParams() != null) lp = new MarginLayoutParams(child.getLayoutParams());
+ else lp = new MarginLayoutParams(mDefaultItemWidth,getHeight());
+
+ if(lp.height == LayoutParams.MATCH_PARENT) lp.height = getHeight();
+ if(lp.width == LayoutParams.MATCH_PARENT) lp.width = getWidth();
+
+ if(lp.height == LayoutParams.WRAP_CONTENT){
+ measureUnspecified(child);
+ lp.height = child.getMeasuredHeight();
+ }
+ if(lp.width == LayoutParams.WRAP_CONTENT){
+ measureUnspecified(child);
+ lp.width = child.getMeasuredWidth();
+ }
+ child.setLayoutParams(lp);
+ }
+
+ private void measureUnspecified(View child){
+ final int pwms = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.UNSPECIFIED);
+ final int phms = MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.UNSPECIFIED);
+ measureChild(child, pwms, phms);
+ }
+
+ /**
+ * Adds a view as a child view and takes care of measuring it
+ *
+ * @param child The view to add
+ * @param layoutMode Either LAYOUT_MODE_LEFT or LAYOUT_MODE_RIGHT
+ * @return child which was actually added to container, subclasses can override to introduce frame views
+ */
+ protected View addAndMeasureChild(final View child, final int layoutMode) {
+ if(child.getLayoutParams() == null) child.setLayoutParams(new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+ final int index = layoutMode == LAYOUT_MODE_TO_BEFORE ? 0 : -1;
+ addViewInLayout(child, index, child.getLayoutParams(), true);
+
+ final int pwms = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
+ final int phms = MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY);
+ measureChild(child, pwms, phms);
+ child.setDrawingCacheEnabled(false);
+
+ return child;
+ }
+
+ /**
+ * Layout children from right to left
+ */
+ protected int layoutChildToBefore(View v, int right , MarginLayoutParams lp){
+ final int left = right - v.getMeasuredWidth() - lp.leftMargin - lp.rightMargin;
+ layoutChild(v, left, lp);
+ return left;
+ }
+
+ /**
+ * @param topline Y coordinate of topline
+ * @param left X coordinate where should we start layout
+ */
+ protected int layoutChild(View v, int left, MarginLayoutParams lp){
+ int l,t,r,b;
+ l = left + lp.leftMargin;
+ t = lp.topMargin;
+ r = l + v.getMeasuredWidth();
+ b = t + v.getMeasuredHeight();
+
+ v.layout(l, t, r, b);
+ return r + lp.rightMargin;
+ }
+
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onTouchEvent will be called and we do the actual
+ * scrolling there.
+ */
+
+
+ /*
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) && (mTouchState == TOUCH_STATE_SCROLLING)) {
+ return true;
+ }
+
+ final float x = ev.getX();
+ final float y = ev.getY();
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ /*
+ * not dragging, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+
+ /*
+ * Locally do absolute value. mLastMotionX is set to the x value
+ * of the down event.
+ */
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+
+ final int touchSlop = mTouchSlop;
+ final boolean xMoved = xDiff > touchSlop;
+
+ if (xMoved) {
+ // Scroll if the user moved far enough along the axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ mHandleSelectionOnActionUp = false;
+ enableChildrenCache();
+ cancelLongPress();
+ }
+
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ // Remember location of down touch
+ mLastMotionX = x;
+
+ mDown.x = (int) x;
+ mDown.y = (int) y;
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTING : TOUCH_STATE_SCROLLING;
+ //if he had normal click in rested state, remember for action up check
+ if(mTouchState == TOUCH_STATE_RESTING){
+ mHandleSelectionOnActionUp = true;
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ mDown.x = -1;
+ mDown.y = -1;
+ break;
+ case MotionEvent.ACTION_UP:
+ //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
+ if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
+ final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
+ if((ev.getEventTime() - ev.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
+ }
+ // Release the drag
+ mHandleSelectionOnActionUp = false;
+ mDown.x = -1;
+ mDown.y = -1;
+
+ mTouchState = TOUCH_STATE_RESTING;
+ clearChildrenCache();
+ break;
+ }
+
+ return mTouchState == TOUCH_STATE_SCROLLING;
+
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+
+ final int action = event.getAction();
+ final float x = event.getX();
+ final float y = event.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.forceFinished(true);
+ }
+
+ // Remember where the motion event started
+ mLastMotionX = x;
+
+ break;
+ case MotionEvent.ACTION_MOVE:
+
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ // Scroll to follow the motion event
+ final int deltaX = (int) (mLastMotionX - x);
+ mLastMotionX = x;
+
+ scrollByDelta(deltaX);
+ }
+ else{
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+
+ final int touchSlop = mTouchSlop;
+ final boolean xMoved = xDiff > touchSlop;
+
+
+ if (xMoved) {
+ // Scroll if the user moved far enough along the axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ enableChildrenCache();
+ cancelLongPress();
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+
+ //this must be here, in case no child view returns true,
+ //events will propagate back here and on intercept touch event wont be called again
+ //in case of no parent it propagates here, in case of parent it usually propagates to on cancel
+ if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
+ final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
+ if((event.getEventTime() - event.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
+ mHandleSelectionOnActionUp = false;
+ }
+
+ //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialXVelocity = (int) mVelocityTracker.getXVelocity();
+ int initialYVelocity = (int) mVelocityTracker.getYVelocity();
+
+ if (Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) {
+ fling(-initialXVelocity, -initialYVelocity);
+ }
+ else{
+ // Release the drag
+ clearChildrenCache();
+ mTouchState = TOUCH_STATE_RESTING;
+
+ mDown.x = -1;
+ mDown.y = -1;
+ }
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+
+ break;
+ }
+
+ // Release the drag
+ clearChildrenCache();
+ mTouchState = TOUCH_STATE_RESTING;
+
+ mDown.x = -1;
+ mDown.y = -1;
+
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ mTouchState = TOUCH_STATE_RESTING;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void computeScroll() {
+ if(mRightEdge != NO_VALUE && mScroller.getFinalX() > mRightEdge - getWidth() + 1){
+ mScroller.setFinalX(mRightEdge - getWidth() + 1);
+ }
+
+ if(mRightEdge != NO_VALUE && getScrollX() > mRightEdge - getWidth()) {
+ if(mRightEdge - getWidth() > 0) scrollTo(mRightEdge - getWidth(), 0);
+ else scrollTo(0, 0);
+ return;
+ }
+
+ if (mScroller.computeScrollOffset()) {
+ if(mScroller.getFinalX() == mScroller.getCurrX()){
+ mScroller.abortAnimation();
+ mTouchState = TOUCH_STATE_RESTING;
+ clearChildrenCache();
+ }
+ else{
+ final int x = mScroller.getCurrX();
+ scrollTo(x, 0);
+
+ postInvalidate();
+ }
+ }
+ else if(mTouchState == TOUCH_STATE_FLING){
+ mTouchState = TOUCH_STATE_RESTING;
+ clearChildrenCache();
+ }
+
+ refill();
+ }
+
+ public void fling(int velocityX, int velocityY){
+ if(isScrollingDisabled) return;
+
+ mTouchState = TOUCH_STATE_FLING;
+ final int x = getScrollX();
+ final int y = getScrollY();
+
+ final int rightInPixels;
+ if(mRightEdge == NO_VALUE) rightInPixels = Integer.MAX_VALUE;
+ else rightInPixels = mRightEdge;
+
+ mScroller.fling(x, y, velocityX, velocityY, 0,rightInPixels - getWidth() + 1,0,0);
+
+ invalidate();
+ }
+
+ protected void scrollByDelta(int deltaX){
+ if(isScrollingDisabled) return;
+
+ final int rightInPixels;
+ if(mRightEdge == NO_VALUE) rightInPixels = Integer.MAX_VALUE;
+ else {
+ rightInPixels = mRightEdge;
+ if(getScrollX() > mRightEdge - getWidth()) {
+ if(mRightEdge - getWidth() > 0) scrollTo(mRightEdge - getWidth(), 0);
+ else scrollTo(0, 0);
+ return;
+ }
+ }
+
+ final int x = getScrollX() + deltaX;
+
+ if(x < 0 ) deltaX -= x;
+ else if(x > rightInPixels - getWidth()) deltaX -= x - (rightInPixels - getWidth());
+
+ scrollBy(deltaX, 0);
+ }
+
+ protected void handleClick(Point p){
+ final int c = getChildCount();
+ View v;
+ final Rect r = new Rect();
+ for(int i=0; i < c; i++){
+ v = getChildAt(i);
+ v.getHitRect(r);
+ if(r.contains(getScrollX() + p.x, getScrollY() + p.y)){
+ if(mItemClickListener != null) mItemClickListener.onItemClick(v);
+ }
+ }
+ }
+
+
+ public void setAdapter(Adapter adapter) {
+ if(mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(mDataObserver);
+ }
+ mAdapter = adapter;
+ mAdapter.registerDataSetObserver(mDataObserver);
+ reset();
+ }
+
+ private void enableChildrenCache() {
+ setChildrenDrawnWithCacheEnabled(true);
+ setChildrenDrawingCacheEnabled(true);
+ }
+
+ private void clearChildrenCache() {
+ setChildrenDrawnWithCacheEnabled(false);
+ }
+
+ @Override
+ protected MarginLayoutParams generateDefaultLayoutParams() {
+ return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ }
+
+ @Override
+ protected MarginLayoutParams generateLayoutParams(LayoutParams p) {
+ return new MarginLayoutParams(p);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(LayoutParams p) {
+ return p instanceof MarginLayoutParams;
+ }
+
+ public void setDefaultItemWidth(int width){
+ //MTODO add xml attributes
+ mDefaultItemWidth = width;
+ }
+
+ /**
+ * Set listener which will fire if item in container is clicked
+ */
+ public void setOnItemClickListener(OnItemClickListener itemClickListener) {
+ this.mItemClickListener = itemClickListener;
+ }
+
+ public void setViewObserver(IViewObserver viewObserver) {
+ this.mViewObserver = viewObserver;
+ }
+
+}
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/HorizontalListWithRemovableItems.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/HorizontalListWithRemovableItems.java
similarity index 93%
rename from MAComponents/src/com/martinappl/components/ui/containers/HorizontalListWithRemovableItems.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/HorizontalListWithRemovableItems.java
index e1cf388..c3306e9 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/HorizontalListWithRemovableItems.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/HorizontalListWithRemovableItems.java
@@ -1,322 +1,322 @@
-package com.martinappl.components.ui.containers;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-import com.martinappl.components.R;
-import com.martinappl.components.general.ToolBox;
-import com.martinappl.components.ui.containers.interfaces.IRemovableItemsAdapterComponent;
-import com.martinappl.components.ui.containers.interfaces.IRemoveFromAdapter;
-
-
-public class HorizontalListWithRemovableItems extends HorizontalList {
- private static final int FADE_TIME = 250;
- private static final int SLIDE_TIME = 350;
-
- private Drawable mRemoveItemIconDrawable = getResources().getDrawable(R.drawable.ico_delete_asset);
- private Drawable mIconForAnimation;
-
- private IRemovableItemsAdapterComponent mRemoveListener;
-
- private int mIconMarginTop = (int) ToolBox.dpToPixels(10, getContext());
- private int mIconMarginRight = (int) ToolBox.dpToPixels(10, getContext());
- private int mIconClickableMarginExtend = (int) ToolBox.dpToPixels(10, getContext());
-
- private int mDownX;
- private int mDownY;
- private boolean isPointerDown;
-
- private View mContainingView;
- private int mContainingViewPosition;
- private int mContainingViewIndex;
- private Object mData;
-
- private final Rect mTempRect = new Rect();
- private int mAnimationLastValue;
-
- private int mAlphaAnimationRunningOnIndex = -1;
-
- private boolean mEditable;
-
- public HorizontalListWithRemovableItems(Context context,
- AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public HorizontalListWithRemovableItems(Context context, AttributeSet attrs) {
- this(context, attrs,0);
- }
-
- public HorizontalListWithRemovableItems(Context context) {
- this(context,null);
- }
-
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
-
- if(!mEditable) return;
-
- final int c = getChildCount();
- final int iw = mRemoveItemIconDrawable.getIntrinsicWidth();
- final int ih = mRemoveItemIconDrawable.getIntrinsicHeight();
-
- View v;
- int r,t;
- Drawable d;
- for(int i = 0; i < c; i++){
- if(i != mAlphaAnimationRunningOnIndex) d = mRemoveItemIconDrawable;
- else d = mIconForAnimation;
-
- v = getChildAt(i);
- r = v.getRight();
- t = v.getTop();
- mTempRect.left = r-iw-mIconMarginRight;
- mTempRect.top = t+mIconMarginTop;
- mTempRect.right = r-mIconMarginRight;
- mTempRect.bottom = t+mIconMarginTop+ih;
- d.setBounds(mTempRect);
- d.draw(canvas);
- }
-
- }
-
-
-
-
- @Override
- protected View addAndMeasureChild(View child, int layoutMode) {
- if(layoutMode == LAYOUT_MODE_TO_BEFORE && mAlphaAnimationRunningOnIndex != -1){
- mAlphaAnimationRunningOnIndex++;
- }
-
- return super.addAndMeasureChild(child, layoutMode);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if(!mEditable || ev.getActionMasked() != MotionEvent.ACTION_DOWN) return super.onInterceptTouchEvent(ev);
- //only down event will get through initial condition
-
- final int x = (int) ev.getX();
- final int y = (int) ev.getY();
-
- final int iw = mRemoveItemIconDrawable.getIntrinsicWidth();
- final int ih = mRemoveItemIconDrawable.getIntrinsicHeight();
-
- View v;
- int r,t;
- final int c = getChildCount();
- for(int i = 0; i < c; i++){
- v = getChildAt(i);
- r = v.getRight();
- t = v.getTop();
- mTempRect.left = r-iw-mIconMarginRight - mIconClickableMarginExtend;
- mTempRect.top = t+mIconMarginTop - mIconClickableMarginExtend;
- mTempRect.right = r-mIconMarginRight + mIconClickableMarginExtend;
- mTempRect.bottom = t+mIconMarginTop+ih + mIconClickableMarginExtend;
-
- if(mTempRect.contains(getScrollX() + x, y)){
- mDownX = x;
- mDownY = y;
- isPointerDown = true;
-
- mContainingView = v;
- mContainingViewPosition = mFirstItemPosition + i;
- mData = mAdapter.getItem(mContainingViewPosition);
- mContainingViewIndex = i;
-
- return true;
- }
- }
-
- isPointerDown = false;
-
- return super.onInterceptTouchEvent(ev);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if(isPointerDown){
- if(ev.getActionMasked() == MotionEvent.ACTION_UP){
- if(ToolBox.getLineLength(ev.getX(), ev.getY(), mDownX, mDownY) < mTouchSlop && mAlphaAnimationRunningOnIndex == -1){
- createRemoveAnimations(mContainingViewIndex).start();
- }
-
- isPointerDown = false;
- }
-
- return true;
- }
- else{
- return super.onTouchEvent(ev);
- }
- }
-
-// protected void refill(){
-// if(mAdapter == null) return;
-//
-// final int leftScreenEdge = getScrollX();
-// int rightScreenEdge = leftScreenEdge + getWidth();
-//
-// if(mAlphaAnimationRunningOnIndex != -1) rightScreenEdge += mContainingView.getWidth();
-//
-// removeNonVisibleViewsLeftToRight(leftScreenEdge);
-// removeNonVisibleViewsRightToLeft(rightScreenEdge);
-//
-// refillLeftToRight(leftScreenEdge, rightScreenEdge);
-// refillRightToLeft(leftScreenEdge);
-// }
-
- private void onRemoveAnimationFinished(int position, View view, Object item){
- if(mRemoveListener == null && mAdapter instanceof IRemoveFromAdapter){
- ((IRemoveFromAdapter) mAdapter).removeItemFromAdapter(position);
- }
- else if(!mRemoveListener.onItemRemove(position,view,item) && mAdapter instanceof IRemoveFromAdapter){
- ((IRemoveFromAdapter) mAdapter).removeItemFromAdapter(position);
- }
-
-
- mContainingView = null;
- mContainingViewIndex = -1;
- mContainingViewPosition = -1;
- mData = null;
- }
-
- private Animator createRemoveAnimations(final int removedViewIndex){
- if(mIconForAnimation == null) mIconForAnimation = mRemoveItemIconDrawable.getConstantState().newDrawable(getResources()).mutate();
- mAlphaAnimationRunningOnIndex = removedViewIndex;
- isScrollingDisabled = true;
- View removed = getChildAt(removedViewIndex);
-
- ObjectAnimator fader = ObjectAnimator.ofFloat(removed, "alpha", 1f, 0f);
- fader.setDuration(FADE_TIME);
- fader.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator anim) {
- mIconForAnimation.setAlpha((int) (255*((Float)anim.getAnimatedValue())));
- invalidate(mIconForAnimation.getBounds());
- }
- });
-
-
- mAnimationLastValue = 0;
- final int distance = removed.getWidth();
- final boolean scrollDuringSlide;
- if(mRightEdge != NO_VALUE && getScrollX() + distance > mRightEdge - getWidth()) scrollDuringSlide = true;
- else scrollDuringSlide = false;
-
- ValueAnimator slider = ValueAnimator.ofInt(0,-distance);
- slider.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator anim) {
- final int val = (Integer) anim.getAnimatedValue();
- int dx = val - mAnimationLastValue;
- mAnimationLastValue = val;
-
- final int c = getChildCount();
- View v;
- for(int i=removedViewIndex+1; i < c; i++){
- v = getChildAt(i);
- v.layout(v.getLeft()+dx, v.getTop(), v.getRight()+dx, v.getBottom());
- }
-
- if(scrollDuringSlide){
- if(getScrollX() + dx < 0) dx = -getScrollX();
- scrollBy(dx, 0);
- }
- }
- });
- slider.setDuration(SLIDE_TIME);
-
-// View v;
-//
-// final float distance = -removed.getWidth();
-// final ArrayList anims = new ArrayList();
-// ObjectAnimator slider = null;
-// for(int i=removedViewIndex+1; i < getChildCount(); i++){
-// v = getChildAt(i);
-// slider = ObjectAnimator.ofFloat(v, "translationX", 0f, distance);
-// anims.add(slider);
-// }
-// if(slider != null) slider.addUpdateListener(new AnimatorUpdateListener() {
-// @Override
-// public void onAnimationUpdate(ValueAnimator anim) {
-// invalidate();
-// }
-// });
-//
-// AnimatorSet sliderSet = new AnimatorSet();
-// sliderSet.playTogether(anims);
-// sliderSet.setDuration(SLIDE_TIME);
-
- final AnimatorListener listener = new AnimatorListener() {
- public void onAnimationStart(Animator arg0) {}
- public void onAnimationRepeat(Animator arg0) {}
- public void onAnimationCancel(Animator arg0) {}
-
- public void onAnimationEnd(Animator arg0) {
- mAlphaAnimationRunningOnIndex = -1;
- isScrollingDisabled = false;
-
- onRemoveAnimationFinished(mContainingViewPosition, mContainingView, mData);
- }
- };
-
-
- AnimatorSet resultSet = new AnimatorSet();
- resultSet.playSequentially(fader,slider);
-
- resultSet.addListener(listener);
-
- return resultSet;
- }
-
-
-
-
- /**
- * Sets icon for overlay which removes item on click
- */
- public void setRemoveItemIcon(int resId){
- mRemoveItemIconDrawable = getResources().getDrawable(resId);
- mIconForAnimation = null;
- }
-
- public void setRemoveItemIconMarginTop(int px){
- mIconMarginTop = px;
- }
-
- public void setRemoveItemIconMarginRight(int px){
- mIconMarginRight = px;
- }
-
- /**
- *
- * @param px
- */
- public void setClickableMarginOfIcon(int px){
- mIconClickableMarginExtend = px;
- }
-
- public void setRemoveItemListener(IRemovableItemsAdapterComponent listener){
- mRemoveListener = listener;
- }
-
- public void setEditable(boolean isEditable){
- mEditable = isEditable;
- }
-
-}
+package it.moondroid.coverflow.components.ui.containers;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import it.moondroid.coverflow.R;
+import it.moondroid.coverflow.components.general.ToolBox;
+import it.moondroid.coverflow.components.ui.containers.interfaces.IRemovableItemsAdapterComponent;
+import it.moondroid.coverflow.components.ui.containers.interfaces.IRemoveFromAdapter;
+
+
+public class HorizontalListWithRemovableItems extends HorizontalList {
+ private static final int FADE_TIME = 250;
+ private static final int SLIDE_TIME = 350;
+
+ private Drawable mRemoveItemIconDrawable = getResources().getDrawable(R.drawable.ico_delete_asset);
+ private Drawable mIconForAnimation;
+
+ private IRemovableItemsAdapterComponent mRemoveListener;
+
+ private int mIconMarginTop = (int) ToolBox.dpToPixels(10, getContext());
+ private int mIconMarginRight = (int) ToolBox.dpToPixels(10, getContext());
+ private int mIconClickableMarginExtend = (int) ToolBox.dpToPixels(10, getContext());
+
+ private int mDownX;
+ private int mDownY;
+ private boolean isPointerDown;
+
+ private View mContainingView;
+ private int mContainingViewPosition;
+ private int mContainingViewIndex;
+ private Object mData;
+
+ private final Rect mTempRect = new Rect();
+ private int mAnimationLastValue;
+
+ private int mAlphaAnimationRunningOnIndex = -1;
+
+ private boolean mEditable;
+
+ public HorizontalListWithRemovableItems(Context context,
+ AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public HorizontalListWithRemovableItems(Context context, AttributeSet attrs) {
+ this(context, attrs,0);
+ }
+
+ public HorizontalListWithRemovableItems(Context context) {
+ this(context,null);
+ }
+
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if(!mEditable) return;
+
+ final int c = getChildCount();
+ final int iw = mRemoveItemIconDrawable.getIntrinsicWidth();
+ final int ih = mRemoveItemIconDrawable.getIntrinsicHeight();
+
+ View v;
+ int r,t;
+ Drawable d;
+ for(int i = 0; i < c; i++){
+ if(i != mAlphaAnimationRunningOnIndex) d = mRemoveItemIconDrawable;
+ else d = mIconForAnimation;
+
+ v = getChildAt(i);
+ r = v.getRight();
+ t = v.getTop();
+ mTempRect.left = r-iw-mIconMarginRight;
+ mTempRect.top = t+mIconMarginTop;
+ mTempRect.right = r-mIconMarginRight;
+ mTempRect.bottom = t+mIconMarginTop+ih;
+ d.setBounds(mTempRect);
+ d.draw(canvas);
+ }
+
+ }
+
+
+
+
+ @Override
+ protected View addAndMeasureChild(View child, int layoutMode) {
+ if(layoutMode == LAYOUT_MODE_TO_BEFORE && mAlphaAnimationRunningOnIndex != -1){
+ mAlphaAnimationRunningOnIndex++;
+ }
+
+ return super.addAndMeasureChild(child, layoutMode);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if(!mEditable || ev.getActionMasked() != MotionEvent.ACTION_DOWN) return super.onInterceptTouchEvent(ev);
+ //only down event will get through initial condition
+
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+
+ final int iw = mRemoveItemIconDrawable.getIntrinsicWidth();
+ final int ih = mRemoveItemIconDrawable.getIntrinsicHeight();
+
+ View v;
+ int r,t;
+ final int c = getChildCount();
+ for(int i = 0; i < c; i++){
+ v = getChildAt(i);
+ r = v.getRight();
+ t = v.getTop();
+ mTempRect.left = r-iw-mIconMarginRight - mIconClickableMarginExtend;
+ mTempRect.top = t+mIconMarginTop - mIconClickableMarginExtend;
+ mTempRect.right = r-mIconMarginRight + mIconClickableMarginExtend;
+ mTempRect.bottom = t+mIconMarginTop+ih + mIconClickableMarginExtend;
+
+ if(mTempRect.contains(getScrollX() + x, y)){
+ mDownX = x;
+ mDownY = y;
+ isPointerDown = true;
+
+ mContainingView = v;
+ mContainingViewPosition = mFirstItemPosition + i;
+ mData = mAdapter.getItem(mContainingViewPosition);
+ mContainingViewIndex = i;
+
+ return true;
+ }
+ }
+
+ isPointerDown = false;
+
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if(isPointerDown){
+ if(ev.getActionMasked() == MotionEvent.ACTION_UP){
+ if(ToolBox.getLineLength(ev.getX(), ev.getY(), mDownX, mDownY) < mTouchSlop && mAlphaAnimationRunningOnIndex == -1){
+ createRemoveAnimations(mContainingViewIndex).start();
+ }
+
+ isPointerDown = false;
+ }
+
+ return true;
+ }
+ else{
+ return super.onTouchEvent(ev);
+ }
+ }
+
+// protected void refill(){
+// if(mAdapter == null) return;
+//
+// final int leftScreenEdge = getScrollX();
+// int rightScreenEdge = leftScreenEdge + getWidth();
+//
+// if(mAlphaAnimationRunningOnIndex != -1) rightScreenEdge += mContainingView.getWidth();
+//
+// removeNonVisibleViewsLeftToRight(leftScreenEdge);
+// removeNonVisibleViewsRightToLeft(rightScreenEdge);
+//
+// refillLeftToRight(leftScreenEdge, rightScreenEdge);
+// refillRightToLeft(leftScreenEdge);
+// }
+
+ private void onRemoveAnimationFinished(int position, View view, Object item){
+ if(mRemoveListener == null && mAdapter instanceof IRemoveFromAdapter){
+ ((IRemoveFromAdapter) mAdapter).removeItemFromAdapter(position);
+ }
+ else if(!mRemoveListener.onItemRemove(position,view,item) && mAdapter instanceof IRemoveFromAdapter){
+ ((IRemoveFromAdapter) mAdapter).removeItemFromAdapter(position);
+ }
+
+
+ mContainingView = null;
+ mContainingViewIndex = -1;
+ mContainingViewPosition = -1;
+ mData = null;
+ }
+
+ private Animator createRemoveAnimations(final int removedViewIndex){
+ if(mIconForAnimation == null) mIconForAnimation = mRemoveItemIconDrawable.getConstantState().newDrawable(getResources()).mutate();
+ mAlphaAnimationRunningOnIndex = removedViewIndex;
+ isScrollingDisabled = true;
+ View removed = getChildAt(removedViewIndex);
+
+ ObjectAnimator fader = ObjectAnimator.ofFloat(removed, "alpha", 1f, 0f);
+ fader.setDuration(FADE_TIME);
+ fader.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator anim) {
+ mIconForAnimation.setAlpha((int) (255*((Float)anim.getAnimatedValue())));
+ invalidate(mIconForAnimation.getBounds());
+ }
+ });
+
+
+ mAnimationLastValue = 0;
+ final int distance = removed.getWidth();
+ final boolean scrollDuringSlide;
+ if(mRightEdge != NO_VALUE && getScrollX() + distance > mRightEdge - getWidth()) scrollDuringSlide = true;
+ else scrollDuringSlide = false;
+
+ ValueAnimator slider = ValueAnimator.ofInt(0,-distance);
+ slider.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator anim) {
+ final int val = (Integer) anim.getAnimatedValue();
+ int dx = val - mAnimationLastValue;
+ mAnimationLastValue = val;
+
+ final int c = getChildCount();
+ View v;
+ for(int i=removedViewIndex+1; i < c; i++){
+ v = getChildAt(i);
+ v.layout(v.getLeft()+dx, v.getTop(), v.getRight()+dx, v.getBottom());
+ }
+
+ if(scrollDuringSlide){
+ if(getScrollX() + dx < 0) dx = -getScrollX();
+ scrollBy(dx, 0);
+ }
+ }
+ });
+ slider.setDuration(SLIDE_TIME);
+
+// View v;
+//
+// final float distance = -removed.getWidth();
+// final ArrayList anims = new ArrayList();
+// ObjectAnimator slider = null;
+// for(int i=removedViewIndex+1; i < getChildCount(); i++){
+// v = getChildAt(i);
+// slider = ObjectAnimator.ofFloat(v, "translationX", 0f, distance);
+// anims.add(slider);
+// }
+// if(slider != null) slider.addUpdateListener(new AnimatorUpdateListener() {
+// @Override
+// public void onAnimationUpdate(ValueAnimator anim) {
+// invalidate();
+// }
+// });
+//
+// AnimatorSet sliderSet = new AnimatorSet();
+// sliderSet.playTogether(anims);
+// sliderSet.setDuration(SLIDE_TIME);
+
+ final AnimatorListener listener = new AnimatorListener() {
+ public void onAnimationStart(Animator arg0) {}
+ public void onAnimationRepeat(Animator arg0) {}
+ public void onAnimationCancel(Animator arg0) {}
+
+ public void onAnimationEnd(Animator arg0) {
+ mAlphaAnimationRunningOnIndex = -1;
+ isScrollingDisabled = false;
+
+ onRemoveAnimationFinished(mContainingViewPosition, mContainingView, mData);
+ }
+ };
+
+
+ AnimatorSet resultSet = new AnimatorSet();
+ resultSet.playSequentially(fader,slider);
+
+ resultSet.addListener(listener);
+
+ return resultSet;
+ }
+
+
+
+
+ /**
+ * Sets icon for overlay which removes item on click
+ */
+ public void setRemoveItemIcon(int resId){
+ mRemoveItemIconDrawable = getResources().getDrawable(resId);
+ mIconForAnimation = null;
+ }
+
+ public void setRemoveItemIconMarginTop(int px){
+ mIconMarginTop = px;
+ }
+
+ public void setRemoveItemIconMarginRight(int px){
+ mIconMarginRight = px;
+ }
+
+ /**
+ *
+ * @param px
+ */
+ public void setClickableMarginOfIcon(int px){
+ mIconClickableMarginExtend = px;
+ }
+
+ public void setRemoveItemListener(IRemovableItemsAdapterComponent listener){
+ mRemoveListener = listener;
+ }
+
+ public void setEditable(boolean isEditable){
+ mEditable = isEditable;
+ }
+
+}
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/contentbands/BasicContentBand.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/contentbands/BasicContentBand.java
similarity index 96%
rename from MAComponents/src/com/martinappl/components/ui/containers/contentbands/BasicContentBand.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/contentbands/BasicContentBand.java
index a0a75b9..1503758 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/contentbands/BasicContentBand.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/contentbands/BasicContentBand.java
@@ -1,1168 +1,1168 @@
-package com.martinappl.components.ui.containers.contentbands;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedList;
-import java.util.List;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.Scroller;
-
-import com.martinappl.components.R;
-import com.martinappl.components.general.ToolBox;
-import com.martinappl.components.general.Validate;
-
-
-/**
- * @author Martin Appl
- *
- * Horizontally scrollable container with boundaries on the ends, which places Views on coordinates specified
- * by tile objects. Data binding is specified by adapter interface. Use abstract adapter which has already implemented
- * algorithms for searching views in requested ranges. You only need to implement getViewForTile method where you map
- * Tile objects from dataset to corresponding View objects, which get displayed. Position on screen is described by LayoutParams object.
- * Method getLayoutParamsForTile helps generate layout params from data objects. If you don't set Layout params in getViewForTile, this
- * methods is called automatically afterwards.
- *
- * DSP = device specific pixel
- */
-public class BasicContentBand extends ViewGroup {
- //CONSTANTS
-// private static final String LOG_TAG = "Basic_ContentBand_Component";
- private static final int NO_VALUE = -11;
- private static final int DSP_DEFAULT = 10;
-
- /** User is not touching the list */
- protected static final int TOUCH_STATE_RESTING = 0;
-
- /** User is scrolling the list */
- protected static final int TOUCH_STATE_SCROLLING = 1;
-
- /** Fling gesture in progress */
- protected static final int TOUCH_STATE_FLING = 2;
-
- /**
- * In this mode we have pixel size of DSP specified, if dspHeight is bigger than window, content band can be scrolled vertically.
- */
- public static final int GRID_MODE_FIXED_SIZE = 0;
- /**
- * In this mode is pixel size of DSP calculated dynamically, based on widget height in pixels and value of dspHeight which is fixed
- * and taken from adapters getBottom method
- */
- public static final int GRID_MODE_DYNAMIC_SIZE = 1;
-
- //to which direction on X axis are window coordinates sliding
- protected static final int DIRECTION_RIGHT = 0;
- protected static final int DIRECTION_LEFT = 1;
-
-
- //VARIABLES
- protected Adapter mAdapter;
- private int mGridMode = GRID_MODE_DYNAMIC_SIZE;
- /**How many normal pixels corresponds to one DSP pixel*/
- private int mDspPixelRatio = DSP_DEFAULT;
- private int mDspHeight = NO_VALUE;
- protected int mDspHeightModulo;
- //refilling
- protected int mCurrentlyLayoutedViewsLeftEdgeDsp;
- protected int mCurrentlyLayoutedViewsRightEdgeDsp;
- private final ArrayList mTempViewArray = new ArrayList();
- //touch, scrolling
- protected int mTouchState = TOUCH_STATE_RESTING;
- private float mLastMotionX;
- private float mLastMotionY;
- private final Point mDown = new Point();
- private VelocityTracker mVelocityTracker;
- protected final Scroller mScroller;
- private boolean mHandleSelectionOnActionUp = false;
- protected int mScrollDirection = NO_VALUE;
- //constant values
- private final int mTouchSlop;
- private final int mMinimumVelocity;
- private final int mMaximumVelocity;
-// private final Rect mTempRect = new Rect();
-
- private boolean mIsZOrderEnabled;
- private int[] mDrawingOrderArray;
-
- //listeners
- private OnItemClickListener mItemClickListener;
-
- public BasicContentBand(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- final ViewConfiguration configuration = ViewConfiguration.get(context);
- mTouchSlop = configuration.getScaledTouchSlop();
- mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
- mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
- mScroller = new Scroller(context);
-
- if(attrs != null){
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BasicContentBand, defStyle, 0);
-
- mDspPixelRatio = a.getInteger(R.styleable.BasicContentBand_deviceSpecificPixelSize, mDspPixelRatio);
- mGridMode = a.getInteger(R.styleable.BasicContentBand_gridMode, mGridMode);
-
- a.recycle();
- }
-
-
- }
-
- public BasicContentBand(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public BasicContentBand(Context context) {
- this(context,null);
- }
-
- protected int dspToPx(int dsp){
- return dsp * mDspPixelRatio;
- }
-
- protected int pxToDsp(int px){
- return px / mDspPixelRatio;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
- int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
-
- if(mAdapter != null){
- mDspHeight = mAdapter.getBottom();
- Validate.isTrue(mDspHeight > 0, "Adapter getBottom must return value greater than zero");
- }
- else{
- setMeasuredDimension(widthSpecSize, heightSpecSize);
- return;
- }
-
- int measuredWidth, measuredHeight;
- if(mGridMode == GRID_MODE_FIXED_SIZE){
- /*HEIGHT*/
- measuredHeight = mDspPixelRatio * mDspHeight;
-
- if(heightSpecMode == MeasureSpec.AT_MOST){
- if(measuredHeight > heightSpecSize) measuredHeight = heightSpecSize;
- }
- else if(heightSpecMode == MeasureSpec.EXACTLY){
- measuredHeight = heightSpecSize;
- }
-
- /*WIDTH*/
- measuredWidth = widthSpecSize;
- if(mAdapter != null) measuredWidth = mAdapter.getEnd() * mDspPixelRatio;
-
- if(widthSpecMode == MeasureSpec.AT_MOST){
- if(measuredWidth > widthSpecSize) measuredWidth = widthSpecSize;
- }
- else if(widthSpecMode == MeasureSpec.EXACTLY){
- measuredWidth = widthSpecSize;
- }
- }
- else{
- if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
- throw new RuntimeException("Can not have unspecified hight dimension in dynamic grid mode");
- }
- /*HEIGHT*/
- measuredHeight = heightSpecSize;
-
- mDspPixelRatio = measuredHeight / mDspHeight;
- mDspHeightModulo = measuredHeight % mDspHeight;
-
- measuredHeight = mDspPixelRatio * mDspHeight;
-
- if(heightSpecMode == MeasureSpec.AT_MOST){
- if(measuredHeight > heightSpecSize) measuredHeight = heightSpecSize;
- else mDspHeightModulo = 0;
- }
- else if(heightSpecMode == MeasureSpec.EXACTLY){
- measuredHeight = heightSpecSize;
- }
-
- /*WIDTH*/
- measuredWidth = widthSpecSize;
- if(mAdapter != null) measuredWidth = mAdapter.getEnd() * mDspPixelRatio;
-
- if(widthSpecMode == MeasureSpec.AT_MOST){
- if(measuredWidth > widthSpecSize) measuredWidth = widthSpecSize;
- }
- else if(widthSpecMode == MeasureSpec.EXACTLY){
- measuredWidth = widthSpecSize;
- }
-
- }
-
- setMeasuredDimension(measuredWidth, measuredHeight);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int c = getChildCount();
-
- if(c == 0) {
- fillEmptyContainer();
- c = getChildCount();
- }
-
- for(int i=0; i= 0; j--){ //start at the end, because mostly we are searching for view which was added to end in previous iterations
-// if(((LayoutParams)getChildAt(j).getLayoutParams()).tileNumber == lp.tileNumber) {
-// arr[i] = null;
-// nullCounter++;
-// break;
-// }
-// }
-// }
-//
-// final View[] res = new View[arr.length - nullCounter];
-// for(int i=0,j=0; i comparator = new Comparator() {
- @Override
- public int compare(View lhs, View rhs) {
- final LayoutParams l = (LayoutParams) lhs.getLayoutParams();
- final LayoutParams r = (LayoutParams) rhs.getLayoutParams();
-
- if(l.z == r.z) return 0;
- else if(l.z < r.z) return -1;
- else return 1;
- }
- };
-
- Arrays.sort(tempArr, comparator);
- mDrawingOrderArray = new int[tempArr.length];
- for(int i=0; i dspMostRight) dspMostRight = lp.getDspRight();
- if(lp.dspLeft < dspMostLeft) dspMostLeft = lp.dspLeft;
- addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
- }
-
- if(mIsZOrderEnabled) rearrangeViewsAccordingZOrder();
-
- mCurrentlyLayoutedViewsLeftEdgeDsp = dspMostLeft;
- mCurrentlyLayoutedViewsRightEdgeDsp= dspMostRight;
- }
-
- /**
- * Checks and refills empty area on the left edge of screen
- */
- protected void refillLeftSide(){
- if(mAdapter == null) return;
-
- final int leftScreenEdge = getScrollX();
- final int dspLeftScreenEdge = pxToDsp(leftScreenEdge);
- final int dspNextViewsRight = mCurrentlyLayoutedViewsLeftEdgeDsp;
-
- if(dspLeftScreenEdge >= dspNextViewsRight) return;
-// Logger.d(LOG_TAG, "from " + dspLeftScreenEdge + ", to " + dspNextViewsRight);
-
- View[] list = mAdapter.getViewsByRightSideRange(dspLeftScreenEdge, dspNextViewsRight);
-// list = filterAlreadyPresentViews(list);
-
- int dspMostLeft = dspNextViewsRight;
- LayoutParams lp;
- for(int i=0; i < list.length; i++){
- lp = (LayoutParams) list[i].getLayoutParams();
- if(lp.dspLeft < dspMostLeft) dspMostLeft = lp.dspLeft;
- addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
- }
-
- if(list.length > 0){
- layoutNewChildren(list);
- }
-
- mCurrentlyLayoutedViewsLeftEdgeDsp = dspMostLeft;
- }
-
- /**
- * Checks and refills empty area on the right
- */
- protected void refillRightSide(){
- if(mAdapter == null) return;
-
- final int rightScreenEdge = getScrollX() + getWidth();
- final int dspNextAddedViewsLeft = mCurrentlyLayoutedViewsRightEdgeDsp;
-
- int dspRightScreenEdge = pxToDsp(rightScreenEdge) + 1;
- if(dspRightScreenEdge > mAdapter.getEnd()) dspRightScreenEdge = mAdapter.getEnd();
-
- if(dspNextAddedViewsLeft >= dspRightScreenEdge) return;
-
- View[] list = mAdapter.getViewsByLeftSideRange(dspNextAddedViewsLeft, dspRightScreenEdge);
-// list = filterAlreadyPresentViews(list);
-
- int dspMostRight = 0;
- LayoutParams lp;
- for(int i=0; i < list.length; i++){
- lp = (LayoutParams) list[i].getLayoutParams();
- if(lp.getDspRight() > dspMostRight) dspMostRight = lp.getDspRight();
- addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
- }
-
- if(list.length > 0){
- layoutNewChildren(list);
- }
-
- mCurrentlyLayoutedViewsRightEdgeDsp = dspMostRight;
- }
-
- /**
- * Remove non visible views laid out of the screen
- */
- private void removeNonVisibleViews(){
- if(getChildCount() == 0) return;
-
- final int leftScreenEdge = getScrollX();
- final int rightScreenEdge = leftScreenEdge + getWidth();
-
- int dspRightScreenEdge = pxToDsp(rightScreenEdge);
- if(dspRightScreenEdge >= 0) dspRightScreenEdge++; //to avoid problem with rounding of values
-
- int dspLeftScreenEdge = pxToDsp(leftScreenEdge);
- if(dspLeftScreenEdge <= 0) dspLeftScreenEdge--; //when values are <0 they get floored to value which is larger
-
- mTempViewArray.clear();
- View v;
- for(int i=0; i dspMostRight) dspMostRight = lp.getDspRight();
- if(lp.dspLeft < dspMostLeft) dspMostLeft = lp.dspLeft;
- }
-
- mCurrentlyLayoutedViewsLeftEdgeDsp = dspMostLeft;
- mCurrentlyLayoutedViewsRightEdgeDsp = dspMostRight;
-
- }
-
- //check if View with specified LayoutParams is currently on screen
- private boolean isOnScreen(LayoutParams lp, int dspLeftScreenEdge, int dspRightScreenEdge){
- final int left = lp.dspLeft;
- final int right = left + lp.dspWidth;
-
- if(right > dspLeftScreenEdge && left < dspRightScreenEdge) return true;
- else return false;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
-
- /*
- * This method JUST determines whether we want to intercept the motion.
- * If we return true, onTouchEvent will be called and we do the actual
- * scrolling there.
- */
-
-
- /*
- * Shortcut the most recurring case: the user is in the dragging
- * state and he is moving his finger. We want to intercept this
- * motion.
- */
- final int action = ev.getAction();
- if ((action == MotionEvent.ACTION_MOVE) && (mTouchState == TOUCH_STATE_SCROLLING)) {
- return true;
- }
-
- final float x = ev.getX();
- final float y = ev.getY();
- switch (action) {
- case MotionEvent.ACTION_MOVE:
- /*
- * not dragging, otherwise the shortcut would have caught it. Check
- * whether the user has moved far enough from his original down touch.
- */
-
- /*
- * Locally do absolute value. mLastMotionX is set to the x value
- * of the down event.
- */
- final int xDiff = (int) Math.abs(x - mLastMotionX);
- final int yDiff = (int) Math.abs(y - mLastMotionY);
-
- final int touchSlop = mTouchSlop;
- final boolean xMoved = xDiff > touchSlop;
- final boolean yMoved = yDiff > touchSlop;
-
-
- if (xMoved || yMoved) {
- // Scroll if the user moved far enough along the axis
- mTouchState = TOUCH_STATE_SCROLLING;
- mHandleSelectionOnActionUp = false;
- enableChildrenCache();
- cancelLongPress();
- }
-
- break;
-
- case MotionEvent.ACTION_DOWN:
- // Remember location of down touch
- mLastMotionX = x;
-
- mDown.x = (int) x;
- mDown.y = (int) y;
-
- /*
- * If being flinged and user touches the screen, initiate drag;
- * otherwise don't. mScroller.isFinished should be false when
- * being flinged.
- */
- mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTING : TOUCH_STATE_SCROLLING;
- //if he had normal click in rested state, remember for action up check
- if(mTouchState == TOUCH_STATE_RESTING){
- mHandleSelectionOnActionUp = true;
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- mDown.x = -1;
- mDown.y = -1;
- break;
- case MotionEvent.ACTION_UP:
- //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
- if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
- final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
- if((ev.getEventTime() - ev.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
- }
- // Release the drag
- mHandleSelectionOnActionUp = false;
- mDown.x = -1;
- mDown.y = -1;
-
- mTouchState = TOUCH_STATE_RESTING;
- clearChildrenCache();
- break;
- }
-
- return mTouchState == TOUCH_STATE_SCROLLING;
-
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
-
- final int action = event.getAction();
- final float x = event.getX();
- final float y = event.getY();
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- /*
- * If being flinged and user touches, stop the fling. isFinished
- * will be false if being flinged.
- */
- if (!mScroller.isFinished()) {
- mScroller.forceFinished(true);
- }
-
- // Remember where the motion event started
- mLastMotionX = x;
- mLastMotionY = y;
-
- break;
- case MotionEvent.ACTION_MOVE:
-
- if (mTouchState == TOUCH_STATE_SCROLLING) {
- // Scroll to follow the motion event
- final int deltaX = (int) (mLastMotionX - x);
- final int deltaY = (int) (mLastMotionY - y);
- mLastMotionX = x;
- mLastMotionY = y;
-
- scrollByDelta(deltaX, deltaY);
- }
- else{
- final int xDiff = (int) Math.abs(x - mLastMotionX);
- final int yDiff = (int) Math.abs(y - mLastMotionY);
-
- final int touchSlop = mTouchSlop;
- final boolean xMoved = xDiff > touchSlop;
- final boolean yMoved = yDiff > touchSlop;
-
-
- if (xMoved || yMoved) {
- // Scroll if the user moved far enough along the axis
- mTouchState = TOUCH_STATE_SCROLLING;
- enableChildrenCache();
- cancelLongPress();
- }
- }
- break;
- case MotionEvent.ACTION_UP:
-
- //this must be here, in case no child view returns true,
- //events will propagate back here and on intercept touch event wont be called again
- //in case of no parent it propagates here, in case of parent it usually propagates to on cancel
- if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
- final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
- if((event.getEventTime() - event.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
- mHandleSelectionOnActionUp = false;
- }
-
- //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
- if (mTouchState == TOUCH_STATE_SCROLLING) {
-
- mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialXVelocity = (int) mVelocityTracker.getXVelocity();
- int initialYVelocity = (int) mVelocityTracker.getYVelocity();
-
- if (Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) {
- fling(-initialXVelocity, -initialYVelocity);
- }
- else{
- // Release the drag
- clearChildrenCache();
- mTouchState = TOUCH_STATE_RESTING;
-
- mDown.x = -1;
- mDown.y = -1;
- }
-
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
-
- break;
- }
-
- // Release the drag
- clearChildrenCache();
- mTouchState = TOUCH_STATE_RESTING;
-
- mDown.x = -1;
- mDown.y = -1;
-
- break;
- case MotionEvent.ACTION_CANCEL:
- mTouchState = TOUCH_STATE_RESTING;
- }
-
- return true;
- }
-
-
- @Override
- public void computeScroll() {
- if (mScroller.computeScrollOffset()) {
- if(mScroller.getFinalX() == mScroller.getCurrX()){
- mScroller.abortAnimation();
- mTouchState = TOUCH_STATE_RESTING;
- mScrollDirection = NO_VALUE;
- clearChildrenCache();
- }
- else{
- final int x = mScroller.getCurrX();
- final int y = mScroller.getCurrY();
- scrollTo(x, y);
-
- postInvalidate();
- }
- }
- else if(mTouchState == TOUCH_STATE_FLING){
- mTouchState = TOUCH_STATE_RESTING;
- mScrollDirection = NO_VALUE;
- clearChildrenCache();
- }
-
- removeNonVisibleViews();
- if(mScrollDirection == DIRECTION_LEFT) refillLeftSide();
- if(mScrollDirection == DIRECTION_RIGHT) refillRightSide();
-
- if(mIsZOrderEnabled) rearrangeViewsAccordingZOrder();
- }
-
- public void fling(int velocityX, int velocityY){
- mTouchState = TOUCH_STATE_FLING;
- final int x = getScrollX();
- final int y = getScrollY();
- final int rightInPixels = dspToPx(mAdapter.getEnd());
- final int bottomInPixels = dspToPx(mAdapter.getBottom()) + mDspHeightModulo;
-
- mScroller.fling(x, y, velocityX, velocityY, 0,rightInPixels - getWidth(),0,bottomInPixels - getHeight());
-
- if(velocityX < 0) {
- mScrollDirection = DIRECTION_LEFT;
- }
- else if(velocityX > 0) {
- mScrollDirection = DIRECTION_RIGHT;
- }
-
-
- invalidate();
- }
-
- protected void scrollByDelta(int deltaX, int deltaY){
- final int rightInPixels = dspToPx(mAdapter.getEnd());
- final int bottomInPixels = dspToPx(mAdapter.getBottom()) + mDspHeightModulo;
- final int x = getScrollX() + deltaX;
- final int y = getScrollY() + deltaY;
-
- if(x < 0 ) deltaX -= x;
- else if(x > rightInPixels - getWidth()) deltaX -= x - (rightInPixels - getWidth());
-
- if(y < 0 ) deltaY -= y;
- else if(y > bottomInPixels - getHeight()) deltaY -= y - (bottomInPixels - getHeight());
-
- if(deltaX < 0) {
- mScrollDirection = DIRECTION_LEFT;
- }
- else {
- mScrollDirection = DIRECTION_RIGHT;
- }
-
- scrollBy(deltaX, deltaY);
- }
-
- protected void handleClick(Point p){
- final int c = getChildCount();
- View v;
- final Rect r = new Rect();
- for(int i=0; i < c; i++){
- v = getChildAt(i);
- v.getHitRect(r);
- if(r.contains(getScrollX() + p.x, getScrollY() + p.y)){
- if(mItemClickListener != null) mItemClickListener.onItemClick(v);
- }
- }
- }
-
- /**
- * Returns current Adapter with backing data
- */
- public Adapter getAdapter() {
- return mAdapter;
- }
-
- /**
- * Set Adapter with backing data
- */
- public void setAdapter(Adapter adapter) {
- this.mAdapter = adapter;
- requestLayout();
- }
-
- /**
- * Set listener which will fire if item in container is clicked
- */
- public void setOnItemClickListener(OnItemClickListener itemClickListener) {
- this.mItemClickListener = itemClickListener;
- }
-
- private void enableChildrenCache() {
- setChildrenDrawingCacheEnabled(true);
- setChildrenDrawnWithCacheEnabled(true);
- }
-
- private void clearChildrenCache() {
- setChildrenDrawnWithCacheEnabled(false);
- }
-
- /**
- * In GRID_MODE_FIXED_SIZE mode has one dsp dimension set by setDspSize(), If band height is after transformation to normal pixels bigger than
- * available space, content becomes scrollable also vertically.
- *
- * In GRID_MODE_DYNAMIC_SIZE is dsp dimension computed from measured height and band height to always
- */
- public void setGridMode(int mode){
- mGridMode = mode;
- }
-
- /**
- * Specifies how many normal pixels is in length of one device specific pixel
- * This method is significant only in GRID_MODE_FIXED_SIZE mode (use setGridMode)
- */
- public void setDspSize(int pixels){
- mDspPixelRatio = pixels;
- }
-
- /**
- * Set to true if you want component to work with tile z parameter;
- * If you don't have any overlapping view, leave it on default false, because computing
- * with z order makes rendering slower.
- */
- public void setZOrderEnabled(boolean enable){
- mIsZOrderEnabled = enable;
- setChildrenDrawingOrderEnabled(enable);
- }
-
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams();
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new LayoutParams();
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams;
- }
-
-//----------------CONTENT BAND END--------------------------------------------------------------------
-
- public interface OnItemClickListener{
- void onItemClick(View v);
- }
-
- public interface Adapter {
-
- /**
- * Return Views which have left edge in device specific coordinates in range from-to,
- * @param from inclusive
- * @param to exclusive
- */
- public abstract View[] getViewsByLeftSideRange(int from, int to);
-
- /**
- * Return Views which have right edge in device specific coordinates in range from-to,
- * @param from exclusive
- * @param to inclusive
- */
- public abstract View[] getViewsByRightSideRange(int from, int to);
-
- /**
- * @return Right Coordinate of last tile in DSP
- */
- int getEnd();
-
- /**
- * @return Bottom Coordinate of tiles on bottom edge in DSP, Must be > 0
- *
- */
- int getBottom();
-
- /**
- * @return total number of tiles
- */
- public int getCount();
-
- /**
- * Makes union between View returned by left side and right side ranges
- * Needed for initialization of component
- */
- public abstract View[] getViewsVisibleInRange(int from, int to);
-
- /**
- * Puts View, which is not needed anymore back to Adapter. View will be used later instead of creating or inflating same view.
- */
- public void offerViewForRecycling(View view);
- }
-
-
- public static class LayoutParams extends ViewGroup.LayoutParams{
- public int tileId;
- public int dspLeft;
- public int dspTop;
- public int dspWidth;
- public int dspHeight;
- public int z;
-
- private int viewgroupIndex;
-
- public LayoutParams() {
- super(NO_VALUE, NO_VALUE);
- }
-
- public int getDspRight(){
- return dspLeft + dspWidth;
- }
- }
-
-
-
- public static abstract class AbstractAdapter implements Adapter{
- private final ViewCache mViewCache = new ViewCache();
-
- protected ArrayList mTilesByBegining;
- protected ArrayList mTilesByEnd;
-// protected SparseArray mTilesByNumber;
- protected IDataListener mChangeListener;
-
- public AbstractAdapter(){}
- public AbstractAdapter(ArrayList tiles){
- initWithNewData(tiles);
- }
-
- private final Comparator beginingComparator = new Comparator() {
- @Override
- public int compare(Tile o1, Tile o2) {
- if(o1.getX() == o2.getX()) return 0;
- else if(o1.getX() < o2.getX()) return -1;
- else return 1;
- }
- };
-
- private final Comparator endComparator = new Comparator() {
- @Override
- public int compare(Tile o1, Tile o2) {
- if(o1.getXRight() == o2.getXRight()) return 0;
- else if(o1.getXRight() < o2.getXRight()) return -1;
- else return 1;
- }
- };
-
- @SuppressWarnings("unchecked")
- @Override
- public void offerViewForRecycling(View view){
- mViewCache.cacheView((V) view);
- }
-
-
- /**
- * Use getLayoutParamsForTile to get correct layout params for Tile data and set them with setLayoutParams before returning View
- * @param t Tile data from datamodel
- * @param recycled View no more used and returned for recycling. Use together with ViewHolder pattern to avoid performance loss
- * in inflating and searching by ids in more complex xml layouts.
- * @return View which will be displayed in component using layout data from Tile
- *
- *
- * public ImageView getViewForTile(Tile t, ImageView recycled) {
- * ImageView iw;
- * if(recycled != null) iw = recycled;
- * else iw = new ImageView(MainActivity.this);
- *
- * iw.setLayoutParams(getLayoutParamsForTile(t));
- * return iw;
- * }
- *
- */
- public abstract V getViewForTile(Tile t, V recycled);
-
- /**
- * @return total number of tiles
- */
- public int getCount(){
- return mTilesByBegining.size();
- }
-
- public int getEnd(){
- if(mTilesByEnd.size() > 0)return mTilesByEnd.get(mTilesByEnd.size()-1).getXRight();
- else return 0;
- }
-
- private void checkAndFixLayoutParams(View v, Tile t){
- if(!(v.getLayoutParams() instanceof LayoutParams)) v.setLayoutParams(getLayoutParamsForTile(t));
- }
-
- @Override
- public View[] getViewsByLeftSideRange(int from, int to) {
- if(from == to) return new View[0];
- final List list = getTilesWithLeftRange(from, to);
-
- final View[] arr = new View[list.size()];
- for(int i=0; i < arr.length; i++){
- Tile t = list.get(i);
- arr[i] = getViewForTile(t, mViewCache.getCachedView());
- checkAndFixLayoutParams(arr[i], t);
- }
-
- return arr;
- }
-
- @Override
- public View[] getViewsByRightSideRange(int from, int to) {
- if(from == to) return new View[0];
- final List list = getTilesWithRightRange(from, to);
-
- final View[] arr = new View[list.size()];
- for(int i=0; i < arr.length; i++){
- Tile t = list.get(i);
- arr[i] = getViewForTile(t, mViewCache.getCachedView());
- checkAndFixLayoutParams(arr[i], t);
- }
-
- return arr;
- }
-
- public View[] getViewsVisibleInRange(int from, int to){
- final List listLeft = getTilesWithLeftRange(from, to);
- final List listRight = getTilesWithRightRange(from, to);
-
- ArrayList union = ToolBox.union(listLeft, listRight);
-
- final View[] arr = new View[union.size()];
- for(int i=0; i < arr.length; i++){
- Tile t = union.get(i);
- arr[i] = getViewForTile(t, mViewCache.getCachedView());
- checkAndFixLayoutParams(arr[i], t);
- }
-
- return arr;
- }
-
- public void setTiles(ArrayList tiles) {
- initWithNewData(tiles);
- if(mChangeListener != null) mChangeListener.onDataSetChanged();
- }
-
- public void setDataChangeListener(IDataListener listener){
- mChangeListener = listener;
- }
-
- @SuppressWarnings("unchecked")
- protected void initWithNewData(ArrayList tiles){
- mTilesByBegining = (ArrayList) tiles.clone();
-
- Collections.sort(mTilesByBegining, beginingComparator);
-
- mTilesByEnd = (ArrayList) mTilesByBegining.clone();
- Collections.sort(mTilesByEnd, endComparator);
- }
-
- /**
- * @param from inclusive
- * @param to exclusive
- */
- public List getTilesWithLeftRange(int from, int to){
- if(mTilesByBegining.size() == 0) return Collections.emptyList();
- final int fromIndex = binarySearchLeftEdges(from);
- if(mTilesByBegining.get(fromIndex).getX() > to) return Collections.emptyList();
-
- int i = fromIndex;
- Tile t = mTilesByBegining.get(i);
- while(t.getX() < to){
- i++;
- if(i < mTilesByBegining.size())t = mTilesByBegining.get(i);
- else break;
- }
-
- return mTilesByBegining.subList(fromIndex, i);
- }
-
- /**
- *
- * @param from exclusive
- * @param to inclusive
- */
- public List getTilesWithRightRange(int from, int to){
- if(mTilesByEnd.size() == 0) return Collections.emptyList();
-
- final int fromIndex = binarySearchRightEdges(from + 1); //from is exclusive
- final int fromRight = mTilesByEnd.get(fromIndex).getXRight();
-
- if(fromRight > to) return Collections.emptyList();
-
- int i = fromIndex;
- Tile t = mTilesByEnd.get(i);
- while(t.getXRight() <= to){
- i++;
- if(i < mTilesByEnd.size()) t = mTilesByEnd.get(i);
- else break;
- }
-
- return mTilesByEnd.subList(fromIndex, i);
- }
-
- /** Continues to split same values until it rests on first of them
- * returns first tile with left equal than value or greater
- */
- private int binarySearchLeftEdges(int value){
- int lo = 0;
- int hi = mTilesByBegining.size() - 1;
- int mid = 0;
- Tile t = null;
- while (lo <= hi) {
- // Key is in a[lo..hi] or not present.
- mid = lo + (hi - lo) / 2;
- t = mTilesByBegining.get(mid);
-
- if (value > t.getX()) lo = mid + 1;
- else hi = mid - 1;
-
- }
-
- while(t != null && t.getX() < value && mid < mTilesByBegining.size()-1){
- mid++;
- t = mTilesByBegining.get(mid);
- }
-
- return mid;
- }
-
- /** Continues to split same values until it rests on first of them
- * returns first tile with right equal than value or greater
- */
- private int binarySearchRightEdges(int value){
- int lo = 0;
- int hi = mTilesByEnd.size() - 1;
- int mid = 0;
- Tile t = null;
- while (lo <= hi) {
- // Key is in a[lo..hi] or not present.
- mid = lo + (hi - lo) / 2;
- t = mTilesByEnd.get(mid);
-
- final int r = t.getXRight();
- if (value > r) lo = mid + 1;
- else hi = mid - 1;
- }
-
- while(t != null && t.getXRight() < value && mid < mTilesByEnd.size()-1){
- mid++;
- t = mTilesByEnd.get(mid);
- }
-
- return mid;
- }
-
-
-
- /**
- * Use this in getViewForTile implementation to provide correctly initialized layout params for component
- * @param t Tile data from datamodel
- * @return ContendBand layout params
- */
- public LayoutParams getLayoutParamsForTile(Tile t){
- LayoutParams lp = new LayoutParams();
- lp.tileId = t.getId();
- lp.dspLeft = t.getX();
- lp.dspTop = t.getY();
- lp.dspWidth = t.getWidth();
- lp.dspHeight = t.getHeight();
- lp.z = t.getZ();
- return lp;
- }
-
-
- interface IDataListener {
- void onDataSetChanged();
- }
-
- }
-
- private static class ViewCache {
- final LinkedList> mCachedItemViews = new LinkedList>();
-
- /**
- * Check if list of weak references has any view still in memory to offer for recycling
- * @return cached view
- */
- T getCachedView(){
- if (mCachedItemViews.size() != 0) {
- T v;
- do{
- v = mCachedItemViews.removeFirst().get();
- }
- while(v == null && mCachedItemViews.size() != 0);
- return v;
- }
- return null;
- }
-
- void cacheView(T v){
- WeakReference ref = new WeakReference(v);
- mCachedItemViews.addLast(ref);
- }
- }
-
-}
+package it.moondroid.coverflow.components.ui.containers.contentbands;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.Scroller;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+import it.moondroid.coverflow.R;
+import it.moondroid.coverflow.components.general.ToolBox;
+import it.moondroid.coverflow.components.general.Validate;
+
+
+/**
+ * @author Martin Appl
+ *
+ * Horizontally scrollable container with boundaries on the ends, which places Views on coordinates specified
+ * by tile objects. Data binding is specified by adapter interface. Use abstract adapter which has already implemented
+ * algorithms for searching views in requested ranges. You only need to implement getViewForTile method where you map
+ * Tile objects from dataset to corresponding View objects, which get displayed. Position on screen is described by LayoutParams object.
+ * Method getLayoutParamsForTile helps generate layout params from data objects. If you don't set Layout params in getViewForTile, this
+ * methods is called automatically afterwards.
+ *
+ * DSP = device specific pixel
+ */
+public class BasicContentBand extends ViewGroup {
+ //CONSTANTS
+// private static final String LOG_TAG = "Basic_ContentBand_Component";
+ private static final int NO_VALUE = -11;
+ private static final int DSP_DEFAULT = 10;
+
+ /** User is not touching the list */
+ protected static final int TOUCH_STATE_RESTING = 0;
+
+ /** User is scrolling the list */
+ protected static final int TOUCH_STATE_SCROLLING = 1;
+
+ /** Fling gesture in progress */
+ protected static final int TOUCH_STATE_FLING = 2;
+
+ /**
+ * In this mode we have pixel size of DSP specified, if dspHeight is bigger than window, content band can be scrolled vertically.
+ */
+ public static final int GRID_MODE_FIXED_SIZE = 0;
+ /**
+ * In this mode is pixel size of DSP calculated dynamically, based on widget height in pixels and value of dspHeight which is fixed
+ * and taken from adapters getBottom method
+ */
+ public static final int GRID_MODE_DYNAMIC_SIZE = 1;
+
+ //to which direction on X axis are window coordinates sliding
+ protected static final int DIRECTION_RIGHT = 0;
+ protected static final int DIRECTION_LEFT = 1;
+
+
+ //VARIABLES
+ protected Adapter mAdapter;
+ private int mGridMode = GRID_MODE_DYNAMIC_SIZE;
+ /**How many normal pixels corresponds to one DSP pixel*/
+ private int mDspPixelRatio = DSP_DEFAULT;
+ private int mDspHeight = NO_VALUE;
+ protected int mDspHeightModulo;
+ //refilling
+ protected int mCurrentlyLayoutedViewsLeftEdgeDsp;
+ protected int mCurrentlyLayoutedViewsRightEdgeDsp;
+ private final ArrayList mTempViewArray = new ArrayList();
+ //touch, scrolling
+ protected int mTouchState = TOUCH_STATE_RESTING;
+ private float mLastMotionX;
+ private float mLastMotionY;
+ private final Point mDown = new Point();
+ private VelocityTracker mVelocityTracker;
+ protected final Scroller mScroller;
+ private boolean mHandleSelectionOnActionUp = false;
+ protected int mScrollDirection = NO_VALUE;
+ //constant values
+ private final int mTouchSlop;
+ private final int mMinimumVelocity;
+ private final int mMaximumVelocity;
+// private final Rect mTempRect = new Rect();
+
+ private boolean mIsZOrderEnabled;
+ private int[] mDrawingOrderArray;
+
+ //listeners
+ private OnItemClickListener mItemClickListener;
+
+ public BasicContentBand(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mScroller = new Scroller(context);
+
+ if(attrs != null){
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BasicContentBand, defStyle, 0);
+
+ mDspPixelRatio = a.getInteger(R.styleable.BasicContentBand_deviceSpecificPixelSize, mDspPixelRatio);
+ mGridMode = a.getInteger(R.styleable.BasicContentBand_gridMode, mGridMode);
+
+ a.recycle();
+ }
+
+
+ }
+
+ public BasicContentBand(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BasicContentBand(Context context) {
+ this(context,null);
+ }
+
+ protected int dspToPx(int dsp){
+ return dsp * mDspPixelRatio;
+ }
+
+ protected int pxToDsp(int px){
+ return px / mDspPixelRatio;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if(mAdapter != null){
+ mDspHeight = mAdapter.getBottom();
+ Validate.isTrue(mDspHeight > 0, "Adapter getBottom must return value greater than zero");
+ }
+ else{
+ setMeasuredDimension(widthSpecSize, heightSpecSize);
+ return;
+ }
+
+ int measuredWidth, measuredHeight;
+ if(mGridMode == GRID_MODE_FIXED_SIZE){
+ /*HEIGHT*/
+ measuredHeight = mDspPixelRatio * mDspHeight;
+
+ if(heightSpecMode == MeasureSpec.AT_MOST){
+ if(measuredHeight > heightSpecSize) measuredHeight = heightSpecSize;
+ }
+ else if(heightSpecMode == MeasureSpec.EXACTLY){
+ measuredHeight = heightSpecSize;
+ }
+
+ /*WIDTH*/
+ measuredWidth = widthSpecSize;
+ if(mAdapter != null) measuredWidth = mAdapter.getEnd() * mDspPixelRatio;
+
+ if(widthSpecMode == MeasureSpec.AT_MOST){
+ if(measuredWidth > widthSpecSize) measuredWidth = widthSpecSize;
+ }
+ else if(widthSpecMode == MeasureSpec.EXACTLY){
+ measuredWidth = widthSpecSize;
+ }
+ }
+ else{
+ if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ throw new RuntimeException("Can not have unspecified hight dimension in dynamic grid mode");
+ }
+ /*HEIGHT*/
+ measuredHeight = heightSpecSize;
+
+ mDspPixelRatio = measuredHeight / mDspHeight;
+ mDspHeightModulo = measuredHeight % mDspHeight;
+
+ measuredHeight = mDspPixelRatio * mDspHeight;
+
+ if(heightSpecMode == MeasureSpec.AT_MOST){
+ if(measuredHeight > heightSpecSize) measuredHeight = heightSpecSize;
+ else mDspHeightModulo = 0;
+ }
+ else if(heightSpecMode == MeasureSpec.EXACTLY){
+ measuredHeight = heightSpecSize;
+ }
+
+ /*WIDTH*/
+ measuredWidth = widthSpecSize;
+ if(mAdapter != null) measuredWidth = mAdapter.getEnd() * mDspPixelRatio;
+
+ if(widthSpecMode == MeasureSpec.AT_MOST){
+ if(measuredWidth > widthSpecSize) measuredWidth = widthSpecSize;
+ }
+ else if(widthSpecMode == MeasureSpec.EXACTLY){
+ measuredWidth = widthSpecSize;
+ }
+
+ }
+
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int c = getChildCount();
+
+ if(c == 0) {
+ fillEmptyContainer();
+ c = getChildCount();
+ }
+
+ for(int i=0; i= 0; j--){ //start at the end, because mostly we are searching for view which was added to end in previous iterations
+// if(((LayoutParams)getChildAt(j).getLayoutParams()).tileNumber == lp.tileNumber) {
+// arr[i] = null;
+// nullCounter++;
+// break;
+// }
+// }
+// }
+//
+// final View[] res = new View[arr.length - nullCounter];
+// for(int i=0,j=0; i comparator = new Comparator() {
+ @Override
+ public int compare(View lhs, View rhs) {
+ final LayoutParams l = (LayoutParams) lhs.getLayoutParams();
+ final LayoutParams r = (LayoutParams) rhs.getLayoutParams();
+
+ if(l.z == r.z) return 0;
+ else if(l.z < r.z) return -1;
+ else return 1;
+ }
+ };
+
+ Arrays.sort(tempArr, comparator);
+ mDrawingOrderArray = new int[tempArr.length];
+ for(int i=0; i dspMostRight) dspMostRight = lp.getDspRight();
+ if(lp.dspLeft < dspMostLeft) dspMostLeft = lp.dspLeft;
+ addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
+ }
+
+ if(mIsZOrderEnabled) rearrangeViewsAccordingZOrder();
+
+ mCurrentlyLayoutedViewsLeftEdgeDsp = dspMostLeft;
+ mCurrentlyLayoutedViewsRightEdgeDsp= dspMostRight;
+ }
+
+ /**
+ * Checks and refills empty area on the left edge of screen
+ */
+ protected void refillLeftSide(){
+ if(mAdapter == null) return;
+
+ final int leftScreenEdge = getScrollX();
+ final int dspLeftScreenEdge = pxToDsp(leftScreenEdge);
+ final int dspNextViewsRight = mCurrentlyLayoutedViewsLeftEdgeDsp;
+
+ if(dspLeftScreenEdge >= dspNextViewsRight) return;
+// Logger.d(LOG_TAG, "from " + dspLeftScreenEdge + ", to " + dspNextViewsRight);
+
+ View[] list = mAdapter.getViewsByRightSideRange(dspLeftScreenEdge, dspNextViewsRight);
+// list = filterAlreadyPresentViews(list);
+
+ int dspMostLeft = dspNextViewsRight;
+ LayoutParams lp;
+ for(int i=0; i < list.length; i++){
+ lp = (LayoutParams) list[i].getLayoutParams();
+ if(lp.dspLeft < dspMostLeft) dspMostLeft = lp.dspLeft;
+ addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
+ }
+
+ if(list.length > 0){
+ layoutNewChildren(list);
+ }
+
+ mCurrentlyLayoutedViewsLeftEdgeDsp = dspMostLeft;
+ }
+
+ /**
+ * Checks and refills empty area on the right
+ */
+ protected void refillRightSide(){
+ if(mAdapter == null) return;
+
+ final int rightScreenEdge = getScrollX() + getWidth();
+ final int dspNextAddedViewsLeft = mCurrentlyLayoutedViewsRightEdgeDsp;
+
+ int dspRightScreenEdge = pxToDsp(rightScreenEdge) + 1;
+ if(dspRightScreenEdge > mAdapter.getEnd()) dspRightScreenEdge = mAdapter.getEnd();
+
+ if(dspNextAddedViewsLeft >= dspRightScreenEdge) return;
+
+ View[] list = mAdapter.getViewsByLeftSideRange(dspNextAddedViewsLeft, dspRightScreenEdge);
+// list = filterAlreadyPresentViews(list);
+
+ int dspMostRight = 0;
+ LayoutParams lp;
+ for(int i=0; i < list.length; i++){
+ lp = (LayoutParams) list[i].getLayoutParams();
+ if(lp.getDspRight() > dspMostRight) dspMostRight = lp.getDspRight();
+ addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
+ }
+
+ if(list.length > 0){
+ layoutNewChildren(list);
+ }
+
+ mCurrentlyLayoutedViewsRightEdgeDsp = dspMostRight;
+ }
+
+ /**
+ * Remove non visible views laid out of the screen
+ */
+ private void removeNonVisibleViews(){
+ if(getChildCount() == 0) return;
+
+ final int leftScreenEdge = getScrollX();
+ final int rightScreenEdge = leftScreenEdge + getWidth();
+
+ int dspRightScreenEdge = pxToDsp(rightScreenEdge);
+ if(dspRightScreenEdge >= 0) dspRightScreenEdge++; //to avoid problem with rounding of values
+
+ int dspLeftScreenEdge = pxToDsp(leftScreenEdge);
+ if(dspLeftScreenEdge <= 0) dspLeftScreenEdge--; //when values are <0 they get floored to value which is larger
+
+ mTempViewArray.clear();
+ View v;
+ for(int i=0; i dspMostRight) dspMostRight = lp.getDspRight();
+ if(lp.dspLeft < dspMostLeft) dspMostLeft = lp.dspLeft;
+ }
+
+ mCurrentlyLayoutedViewsLeftEdgeDsp = dspMostLeft;
+ mCurrentlyLayoutedViewsRightEdgeDsp = dspMostRight;
+
+ }
+
+ //check if View with specified LayoutParams is currently on screen
+ private boolean isOnScreen(LayoutParams lp, int dspLeftScreenEdge, int dspRightScreenEdge){
+ final int left = lp.dspLeft;
+ final int right = left + lp.dspWidth;
+
+ if(right > dspLeftScreenEdge && left < dspRightScreenEdge) return true;
+ else return false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onTouchEvent will be called and we do the actual
+ * scrolling there.
+ */
+
+
+ /*
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) && (mTouchState == TOUCH_STATE_SCROLLING)) {
+ return true;
+ }
+
+ final float x = ev.getX();
+ final float y = ev.getY();
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ /*
+ * not dragging, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+
+ /*
+ * Locally do absolute value. mLastMotionX is set to the x value
+ * of the down event.
+ */
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+ final int touchSlop = mTouchSlop;
+ final boolean xMoved = xDiff > touchSlop;
+ final boolean yMoved = yDiff > touchSlop;
+
+
+ if (xMoved || yMoved) {
+ // Scroll if the user moved far enough along the axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ mHandleSelectionOnActionUp = false;
+ enableChildrenCache();
+ cancelLongPress();
+ }
+
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ // Remember location of down touch
+ mLastMotionX = x;
+
+ mDown.x = (int) x;
+ mDown.y = (int) y;
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ mTouchState = mScroller.isFinished() ? TOUCH_STATE_RESTING : TOUCH_STATE_SCROLLING;
+ //if he had normal click in rested state, remember for action up check
+ if(mTouchState == TOUCH_STATE_RESTING){
+ mHandleSelectionOnActionUp = true;
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ mDown.x = -1;
+ mDown.y = -1;
+ break;
+ case MotionEvent.ACTION_UP:
+ //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
+ if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
+ final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
+ if((ev.getEventTime() - ev.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
+ }
+ // Release the drag
+ mHandleSelectionOnActionUp = false;
+ mDown.x = -1;
+ mDown.y = -1;
+
+ mTouchState = TOUCH_STATE_RESTING;
+ clearChildrenCache();
+ break;
+ }
+
+ return mTouchState == TOUCH_STATE_SCROLLING;
+
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+
+ final int action = event.getAction();
+ final float x = event.getX();
+ final float y = event.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.forceFinished(true);
+ }
+
+ // Remember where the motion event started
+ mLastMotionX = x;
+ mLastMotionY = y;
+
+ break;
+ case MotionEvent.ACTION_MOVE:
+
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ // Scroll to follow the motion event
+ final int deltaX = (int) (mLastMotionX - x);
+ final int deltaY = (int) (mLastMotionY - y);
+ mLastMotionX = x;
+ mLastMotionY = y;
+
+ scrollByDelta(deltaX, deltaY);
+ }
+ else{
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+ final int touchSlop = mTouchSlop;
+ final boolean xMoved = xDiff > touchSlop;
+ final boolean yMoved = yDiff > touchSlop;
+
+
+ if (xMoved || yMoved) {
+ // Scroll if the user moved far enough along the axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ enableChildrenCache();
+ cancelLongPress();
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+
+ //this must be here, in case no child view returns true,
+ //events will propagate back here and on intercept touch event wont be called again
+ //in case of no parent it propagates here, in case of parent it usually propagates to on cancel
+ if(mHandleSelectionOnActionUp && mTouchState == TOUCH_STATE_RESTING){
+ final float d = ToolBox.getLineLength(mDown.x, mDown.y, x, y);
+ if((event.getEventTime() - event.getDownTime()) < ViewConfiguration.getLongPressTimeout() && d < mTouchSlop) handleClick(mDown);
+ mHandleSelectionOnActionUp = false;
+ }
+
+ //if we had normal down click and we haven't moved enough to initiate drag, take action as a click on down coordinates
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialXVelocity = (int) mVelocityTracker.getXVelocity();
+ int initialYVelocity = (int) mVelocityTracker.getYVelocity();
+
+ if (Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) {
+ fling(-initialXVelocity, -initialYVelocity);
+ }
+ else{
+ // Release the drag
+ clearChildrenCache();
+ mTouchState = TOUCH_STATE_RESTING;
+
+ mDown.x = -1;
+ mDown.y = -1;
+ }
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+
+ break;
+ }
+
+ // Release the drag
+ clearChildrenCache();
+ mTouchState = TOUCH_STATE_RESTING;
+
+ mDown.x = -1;
+ mDown.y = -1;
+
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ mTouchState = TOUCH_STATE_RESTING;
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ if(mScroller.getFinalX() == mScroller.getCurrX()){
+ mScroller.abortAnimation();
+ mTouchState = TOUCH_STATE_RESTING;
+ mScrollDirection = NO_VALUE;
+ clearChildrenCache();
+ }
+ else{
+ final int x = mScroller.getCurrX();
+ final int y = mScroller.getCurrY();
+ scrollTo(x, y);
+
+ postInvalidate();
+ }
+ }
+ else if(mTouchState == TOUCH_STATE_FLING){
+ mTouchState = TOUCH_STATE_RESTING;
+ mScrollDirection = NO_VALUE;
+ clearChildrenCache();
+ }
+
+ removeNonVisibleViews();
+ if(mScrollDirection == DIRECTION_LEFT) refillLeftSide();
+ if(mScrollDirection == DIRECTION_RIGHT) refillRightSide();
+
+ if(mIsZOrderEnabled) rearrangeViewsAccordingZOrder();
+ }
+
+ public void fling(int velocityX, int velocityY){
+ mTouchState = TOUCH_STATE_FLING;
+ final int x = getScrollX();
+ final int y = getScrollY();
+ final int rightInPixels = dspToPx(mAdapter.getEnd());
+ final int bottomInPixels = dspToPx(mAdapter.getBottom()) + mDspHeightModulo;
+
+ mScroller.fling(x, y, velocityX, velocityY, 0,rightInPixels - getWidth(),0,bottomInPixels - getHeight());
+
+ if(velocityX < 0) {
+ mScrollDirection = DIRECTION_LEFT;
+ }
+ else if(velocityX > 0) {
+ mScrollDirection = DIRECTION_RIGHT;
+ }
+
+
+ invalidate();
+ }
+
+ protected void scrollByDelta(int deltaX, int deltaY){
+ final int rightInPixels = dspToPx(mAdapter.getEnd());
+ final int bottomInPixels = dspToPx(mAdapter.getBottom()) + mDspHeightModulo;
+ final int x = getScrollX() + deltaX;
+ final int y = getScrollY() + deltaY;
+
+ if(x < 0 ) deltaX -= x;
+ else if(x > rightInPixels - getWidth()) deltaX -= x - (rightInPixels - getWidth());
+
+ if(y < 0 ) deltaY -= y;
+ else if(y > bottomInPixels - getHeight()) deltaY -= y - (bottomInPixels - getHeight());
+
+ if(deltaX < 0) {
+ mScrollDirection = DIRECTION_LEFT;
+ }
+ else {
+ mScrollDirection = DIRECTION_RIGHT;
+ }
+
+ scrollBy(deltaX, deltaY);
+ }
+
+ protected void handleClick(Point p){
+ final int c = getChildCount();
+ View v;
+ final Rect r = new Rect();
+ for(int i=0; i < c; i++){
+ v = getChildAt(i);
+ v.getHitRect(r);
+ if(r.contains(getScrollX() + p.x, getScrollY() + p.y)){
+ if(mItemClickListener != null) mItemClickListener.onItemClick(v);
+ }
+ }
+ }
+
+ /**
+ * Returns current Adapter with backing data
+ */
+ public Adapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Set Adapter with backing data
+ */
+ public void setAdapter(Adapter adapter) {
+ this.mAdapter = adapter;
+ requestLayout();
+ }
+
+ /**
+ * Set listener which will fire if item in container is clicked
+ */
+ public void setOnItemClickListener(OnItemClickListener itemClickListener) {
+ this.mItemClickListener = itemClickListener;
+ }
+
+ private void enableChildrenCache() {
+ setChildrenDrawingCacheEnabled(true);
+ setChildrenDrawnWithCacheEnabled(true);
+ }
+
+ private void clearChildrenCache() {
+ setChildrenDrawnWithCacheEnabled(false);
+ }
+
+ /**
+ * In GRID_MODE_FIXED_SIZE mode has one dsp dimension set by setDspSize(), If band height is after transformation to normal pixels bigger than
+ * available space, content becomes scrollable also vertically.
+ *
+ * In GRID_MODE_DYNAMIC_SIZE is dsp dimension computed from measured height and band height to always
+ */
+ public void setGridMode(int mode){
+ mGridMode = mode;
+ }
+
+ /**
+ * Specifies how many normal pixels is in length of one device specific pixel
+ * This method is significant only in GRID_MODE_FIXED_SIZE mode (use setGridMode)
+ */
+ public void setDspSize(int pixels){
+ mDspPixelRatio = pixels;
+ }
+
+ /**
+ * Set to true if you want component to work with tile z parameter;
+ * If you don't have any overlapping view, leave it on default false, because computing
+ * with z order makes rendering slower.
+ */
+ public void setZOrderEnabled(boolean enable){
+ mIsZOrderEnabled = enable;
+ setChildrenDrawingOrderEnabled(enable);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams();
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new LayoutParams();
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams;
+ }
+
+//----------------CONTENT BAND END--------------------------------------------------------------------
+
+ public interface OnItemClickListener{
+ void onItemClick(View v);
+ }
+
+ public interface Adapter {
+
+ /**
+ * Return Views which have left edge in device specific coordinates in range from-to,
+ * @param from inclusive
+ * @param to exclusive
+ */
+ public abstract View[] getViewsByLeftSideRange(int from, int to);
+
+ /**
+ * Return Views which have right edge in device specific coordinates in range from-to,
+ * @param from exclusive
+ * @param to inclusive
+ */
+ public abstract View[] getViewsByRightSideRange(int from, int to);
+
+ /**
+ * @return Right Coordinate of last tile in DSP
+ */
+ int getEnd();
+
+ /**
+ * @return Bottom Coordinate of tiles on bottom edge in DSP, Must be > 0
+ *
+ */
+ int getBottom();
+
+ /**
+ * @return total number of tiles
+ */
+ public int getCount();
+
+ /**
+ * Makes union between View returned by left side and right side ranges
+ * Needed for initialization of component
+ */
+ public abstract View[] getViewsVisibleInRange(int from, int to);
+
+ /**
+ * Puts View, which is not needed anymore back to Adapter. View will be used later instead of creating or inflating same view.
+ */
+ public void offerViewForRecycling(View view);
+ }
+
+
+ public static class LayoutParams extends ViewGroup.LayoutParams{
+ public int tileId;
+ public int dspLeft;
+ public int dspTop;
+ public int dspWidth;
+ public int dspHeight;
+ public int z;
+
+ private int viewgroupIndex;
+
+ public LayoutParams() {
+ super(NO_VALUE, NO_VALUE);
+ }
+
+ public int getDspRight(){
+ return dspLeft + dspWidth;
+ }
+ }
+
+
+
+ public static abstract class AbstractAdapter implements Adapter{
+ private final ViewCache mViewCache = new ViewCache();
+
+ protected ArrayList mTilesByBegining;
+ protected ArrayList mTilesByEnd;
+// protected SparseArray mTilesByNumber;
+ protected IDataListener mChangeListener;
+
+ public AbstractAdapter(){}
+ public AbstractAdapter(ArrayList tiles){
+ initWithNewData(tiles);
+ }
+
+ private final Comparator beginingComparator = new Comparator() {
+ @Override
+ public int compare(Tile o1, Tile o2) {
+ if(o1.getX() == o2.getX()) return 0;
+ else if(o1.getX() < o2.getX()) return -1;
+ else return 1;
+ }
+ };
+
+ private final Comparator endComparator = new Comparator() {
+ @Override
+ public int compare(Tile o1, Tile o2) {
+ if(o1.getXRight() == o2.getXRight()) return 0;
+ else if(o1.getXRight() < o2.getXRight()) return -1;
+ else return 1;
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void offerViewForRecycling(View view){
+ mViewCache.cacheView((V) view);
+ }
+
+
+ /**
+ * Use getLayoutParamsForTile to get correct layout params for Tile data and set them with setLayoutParams before returning View
+ * @param t Tile data from datamodel
+ * @param recycled View no more used and returned for recycling. Use together with ViewHolder pattern to avoid performance loss
+ * in inflating and searching by ids in more complex xml layouts.
+ * @return View which will be displayed in component using layout data from Tile
+ *
+ *
+ * public ImageView getViewForTile(Tile t, ImageView recycled) {
+ * ImageView iw;
+ * if(recycled != null) iw = recycled;
+ * else iw = new ImageView(MainActivity.this);
+ *
+ * iw.setLayoutParams(getLayoutParamsForTile(t));
+ * return iw;
+ * }
+ *
+ */
+ public abstract V getViewForTile(Tile t, V recycled);
+
+ /**
+ * @return total number of tiles
+ */
+ public int getCount(){
+ return mTilesByBegining.size();
+ }
+
+ public int getEnd(){
+ if(mTilesByEnd.size() > 0)return mTilesByEnd.get(mTilesByEnd.size()-1).getXRight();
+ else return 0;
+ }
+
+ private void checkAndFixLayoutParams(View v, Tile t){
+ if(!(v.getLayoutParams() instanceof LayoutParams)) v.setLayoutParams(getLayoutParamsForTile(t));
+ }
+
+ @Override
+ public View[] getViewsByLeftSideRange(int from, int to) {
+ if(from == to) return new View[0];
+ final List list = getTilesWithLeftRange(from, to);
+
+ final View[] arr = new View[list.size()];
+ for(int i=0; i < arr.length; i++){
+ Tile t = list.get(i);
+ arr[i] = getViewForTile(t, mViewCache.getCachedView());
+ checkAndFixLayoutParams(arr[i], t);
+ }
+
+ return arr;
+ }
+
+ @Override
+ public View[] getViewsByRightSideRange(int from, int to) {
+ if(from == to) return new View[0];
+ final List list = getTilesWithRightRange(from, to);
+
+ final View[] arr = new View[list.size()];
+ for(int i=0; i < arr.length; i++){
+ Tile t = list.get(i);
+ arr[i] = getViewForTile(t, mViewCache.getCachedView());
+ checkAndFixLayoutParams(arr[i], t);
+ }
+
+ return arr;
+ }
+
+ public View[] getViewsVisibleInRange(int from, int to){
+ final List listLeft = getTilesWithLeftRange(from, to);
+ final List listRight = getTilesWithRightRange(from, to);
+
+ ArrayList union = ToolBox.union(listLeft, listRight);
+
+ final View[] arr = new View[union.size()];
+ for(int i=0; i < arr.length; i++){
+ Tile t = union.get(i);
+ arr[i] = getViewForTile(t, mViewCache.getCachedView());
+ checkAndFixLayoutParams(arr[i], t);
+ }
+
+ return arr;
+ }
+
+ public void setTiles(ArrayList tiles) {
+ initWithNewData(tiles);
+ if(mChangeListener != null) mChangeListener.onDataSetChanged();
+ }
+
+ public void setDataChangeListener(IDataListener listener){
+ mChangeListener = listener;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void initWithNewData(ArrayList tiles){
+ mTilesByBegining = (ArrayList) tiles.clone();
+
+ Collections.sort(mTilesByBegining, beginingComparator);
+
+ mTilesByEnd = (ArrayList) mTilesByBegining.clone();
+ Collections.sort(mTilesByEnd, endComparator);
+ }
+
+ /**
+ * @param from inclusive
+ * @param to exclusive
+ */
+ public List getTilesWithLeftRange(int from, int to){
+ if(mTilesByBegining.size() == 0) return Collections.emptyList();
+ final int fromIndex = binarySearchLeftEdges(from);
+ if(mTilesByBegining.get(fromIndex).getX() > to) return Collections.emptyList();
+
+ int i = fromIndex;
+ Tile t = mTilesByBegining.get(i);
+ while(t.getX() < to){
+ i++;
+ if(i < mTilesByBegining.size())t = mTilesByBegining.get(i);
+ else break;
+ }
+
+ return mTilesByBegining.subList(fromIndex, i);
+ }
+
+ /**
+ *
+ * @param from exclusive
+ * @param to inclusive
+ */
+ public List getTilesWithRightRange(int from, int to){
+ if(mTilesByEnd.size() == 0) return Collections.emptyList();
+
+ final int fromIndex = binarySearchRightEdges(from + 1); //from is exclusive
+ final int fromRight = mTilesByEnd.get(fromIndex).getXRight();
+
+ if(fromRight > to) return Collections.emptyList();
+
+ int i = fromIndex;
+ Tile t = mTilesByEnd.get(i);
+ while(t.getXRight() <= to){
+ i++;
+ if(i < mTilesByEnd.size()) t = mTilesByEnd.get(i);
+ else break;
+ }
+
+ return mTilesByEnd.subList(fromIndex, i);
+ }
+
+ /** Continues to split same values until it rests on first of them
+ * returns first tile with left equal than value or greater
+ */
+ private int binarySearchLeftEdges(int value){
+ int lo = 0;
+ int hi = mTilesByBegining.size() - 1;
+ int mid = 0;
+ Tile t = null;
+ while (lo <= hi) {
+ // Key is in a[lo..hi] or not present.
+ mid = lo + (hi - lo) / 2;
+ t = mTilesByBegining.get(mid);
+
+ if (value > t.getX()) lo = mid + 1;
+ else hi = mid - 1;
+
+ }
+
+ while(t != null && t.getX() < value && mid < mTilesByBegining.size()-1){
+ mid++;
+ t = mTilesByBegining.get(mid);
+ }
+
+ return mid;
+ }
+
+ /** Continues to split same values until it rests on first of them
+ * returns first tile with right equal than value or greater
+ */
+ private int binarySearchRightEdges(int value){
+ int lo = 0;
+ int hi = mTilesByEnd.size() - 1;
+ int mid = 0;
+ Tile t = null;
+ while (lo <= hi) {
+ // Key is in a[lo..hi] or not present.
+ mid = lo + (hi - lo) / 2;
+ t = mTilesByEnd.get(mid);
+
+ final int r = t.getXRight();
+ if (value > r) lo = mid + 1;
+ else hi = mid - 1;
+ }
+
+ while(t != null && t.getXRight() < value && mid < mTilesByEnd.size()-1){
+ mid++;
+ t = mTilesByEnd.get(mid);
+ }
+
+ return mid;
+ }
+
+
+
+ /**
+ * Use this in getViewForTile implementation to provide correctly initialized layout params for component
+ * @param t Tile data from datamodel
+ * @return ContendBand layout params
+ */
+ public LayoutParams getLayoutParamsForTile(Tile t){
+ LayoutParams lp = new LayoutParams();
+ lp.tileId = t.getId();
+ lp.dspLeft = t.getX();
+ lp.dspTop = t.getY();
+ lp.dspWidth = t.getWidth();
+ lp.dspHeight = t.getHeight();
+ lp.z = t.getZ();
+ return lp;
+ }
+
+
+ interface IDataListener {
+ void onDataSetChanged();
+ }
+
+ }
+
+ private static class ViewCache {
+ final LinkedList> mCachedItemViews = new LinkedList>();
+
+ /**
+ * Check if list of weak references has any view still in memory to offer for recycling
+ * @return cached view
+ */
+ T getCachedView(){
+ if (mCachedItemViews.size() != 0) {
+ T v;
+ do{
+ v = mCachedItemViews.removeFirst().get();
+ }
+ while(v == null && mCachedItemViews.size() != 0);
+ return v;
+ }
+ return null;
+ }
+
+ void cacheView(T v){
+ WeakReference ref = new WeakReference(v);
+ mCachedItemViews.addLast(ref);
+ }
+ }
+
+}
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/contentbands/EndlessContentBand.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/contentbands/EndlessContentBand.java
similarity index 94%
rename from MAComponents/src/com/martinappl/components/ui/containers/contentbands/EndlessContentBand.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/contentbands/EndlessContentBand.java
index 524f840..9f76244 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/contentbands/EndlessContentBand.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/contentbands/EndlessContentBand.java
@@ -1,195 +1,196 @@
-package com.martinappl.components.ui.containers.contentbands;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.martinappl.components.general.ToolBox;
-
-/**
- * @author Martin Appl
- * DSP = device specific pixel
- * TODO last poster is disappearing prematurely and reappearing late. Time to time container isn't drawn after Activity initialization.
- */
-public class EndlessContentBand extends BasicContentBand {
-
- public EndlessContentBand(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public EndlessContentBand(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public EndlessContentBand(Context context) {
- super(context);
- }
-
-
-
- /**
- * Checks and refills empty area on the left edge of screen
- */
- @Override
- protected void refillLeftSide(){
- final int leftScreenEdge = getScrollX();
- final int dspNextViewsRight = mCurrentlyLayoutedViewsLeftEdgeDsp;
-
- int dspLeftScreenEdge = pxToDsp(leftScreenEdge);
- if(dspLeftScreenEdge <= 0) dspLeftScreenEdge--; //when values are <0 they get floored to value which is larger
-
- int end = mAdapter.getEnd();
-
- if(dspLeftScreenEdge >= dspNextViewsRight || end == 0) return;
-
- int dspModuloLeftScreenEdge = dspLeftScreenEdge % end;
- int dspModuloNextViewsRight = dspNextViewsRight % end;
- int dspOffsetLeftScreenEdge = dspLeftScreenEdge / end;
- int dspOffsetNextViewsRight = dspNextViewsRight / end;
-
- if(dspModuloLeftScreenEdge < 0) {
- dspModuloLeftScreenEdge += end;
- dspOffsetLeftScreenEdge -= 1;
- }
- if(dspModuloNextViewsRight < 0){
- dspModuloNextViewsRight += end;
- dspOffsetNextViewsRight -= 1;
- }
-
- View[] list;
- if(dspModuloLeftScreenEdge > dspModuloNextViewsRight){
- View[] list1,list2;
- list1 = mAdapter.getViewsByRightSideRange(dspModuloLeftScreenEdge, end);
- list2 = mAdapter.getViewsByRightSideRange(0, dspModuloNextViewsRight);
- translateLayoutParams(list1, dspOffsetLeftScreenEdge);
- translateLayoutParams(list2, dspOffsetNextViewsRight);
-
- list = ToolBox.concatenateArray(list1,list2);
- }
- else{
- list = mAdapter.getViewsByRightSideRange(dspModuloLeftScreenEdge, dspModuloNextViewsRight);
- translateLayoutParams(list, dspOffsetLeftScreenEdge);
- }
-
- int dspMostLeft = dspNextViewsRight;
- LayoutParams lp;
- for(int i=0; i < list.length; i++){
- lp = (LayoutParams) list[i].getLayoutParams();
- if(lp.dspLeft < dspMostLeft) dspMostLeft = lp.dspLeft;
- addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
- }
-
- if(list.length > 0){
- layoutNewChildren(list);
- }
-
- mCurrentlyLayoutedViewsLeftEdgeDsp = dspMostLeft;
- }
-
- private void translateLayoutParams(View[] list,int offset){
- if(offset == 0 || list.length == 0) return;
-
- final int end = mAdapter.getEnd();
- LayoutParams lp;
-
- for(int i=0; i= 0) dspRightScreenEdge++; //to avoid problem with rounding of values
-
- if(dspNextAddedViewsLeft >= dspRightScreenEdge || end == 0) return;
-
- int dspModuloRightScreenEdge = dspRightScreenEdge % end;
- int dspModuloNextAddedViewsLeft = dspNextAddedViewsLeft % end;
- int dspOffsetRightScreenEdge = dspRightScreenEdge / end;
- int dspOffsetNextAddedViewsLeft = dspNextAddedViewsLeft / end;
-
- if(dspModuloRightScreenEdge < 0) {
- dspModuloRightScreenEdge += end;
- dspOffsetRightScreenEdge -= 1;
- }
- if(dspModuloNextAddedViewsLeft < 0) {
- dspModuloNextAddedViewsLeft += end;
- dspOffsetNextAddedViewsLeft -= 1;
- }
-
- View[] list;
- if(dspModuloNextAddedViewsLeft > dspModuloRightScreenEdge){
- View[] list1,list2;
- list1 = mAdapter.getViewsByLeftSideRange(dspModuloNextAddedViewsLeft, end);
- list2 = mAdapter.getViewsByLeftSideRange(0, dspModuloRightScreenEdge);
- translateLayoutParams(list1, dspOffsetNextAddedViewsLeft);
- translateLayoutParams(list2, dspOffsetRightScreenEdge);
-
- list = ToolBox.concatenateArray(list1,list2);
- }
- else{
- list = mAdapter.getViewsByLeftSideRange(dspModuloNextAddedViewsLeft, dspModuloRightScreenEdge);
- translateLayoutParams(list, dspOffsetNextAddedViewsLeft);
- }
-
- int dspMostRight = 0;
- LayoutParams lp;
- for(int i=0; i < list.length; i++){
- lp = (LayoutParams) list[i].getLayoutParams();
- if(lp.getDspRight() > dspMostRight) dspMostRight = lp.getDspRight();
- addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
- }
-
- if(list.length > 0){
- layoutNewChildren(list);
- }
-
- mCurrentlyLayoutedViewsRightEdgeDsp = dspMostRight;
- }
-
- public void fling(int velocityX, int velocityY){
- mTouchState = TOUCH_STATE_FLING;
- final int x = getScrollX();
- final int y = getScrollY();
- final int bottomInPixels = dspToPx(mAdapter.getBottom()) + mDspHeightModulo;
-
- mScroller.fling(x, y, velocityX, velocityY, Integer.MIN_VALUE,Integer.MAX_VALUE, 0, bottomInPixels - getHeight());
-
- if(velocityX < 0) {
- mScrollDirection = DIRECTION_LEFT;
- }
- else if(velocityX > 0) {
- mScrollDirection = DIRECTION_RIGHT;
- }
-
- invalidate();
- }
-
- @Override
- protected void scrollByDelta(int deltaX, int deltaY){
- final int bottomInPixels = dspToPx(mAdapter.getBottom()) + mDspHeightModulo;
- final int y = getScrollY() + deltaY;
-
- if(y < 0 ) deltaY -= y;
- else if(y > bottomInPixels - getHeight()) deltaY -= y - (bottomInPixels - getHeight());
-
- if(deltaX < 0) {
- mScrollDirection = DIRECTION_LEFT;
- }
- else {
- mScrollDirection = DIRECTION_RIGHT;
- }
-
- scrollBy(deltaX, deltaY);
- }
-
-}
+package it.moondroid.coverflow.components.ui.containers.contentbands;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import it.moondroid.coverflow.components.general.ToolBox;
+
+
+/**
+ * @author Martin Appl
+ * DSP = device specific pixel
+ * TODO last poster is disappearing prematurely and reappearing late. Time to time container isn't drawn after Activity initialization.
+ */
+public class EndlessContentBand extends BasicContentBand {
+
+ public EndlessContentBand(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public EndlessContentBand(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public EndlessContentBand(Context context) {
+ super(context);
+ }
+
+
+
+ /**
+ * Checks and refills empty area on the left edge of screen
+ */
+ @Override
+ protected void refillLeftSide(){
+ final int leftScreenEdge = getScrollX();
+ final int dspNextViewsRight = mCurrentlyLayoutedViewsLeftEdgeDsp;
+
+ int dspLeftScreenEdge = pxToDsp(leftScreenEdge);
+ if(dspLeftScreenEdge <= 0) dspLeftScreenEdge--; //when values are <0 they get floored to value which is larger
+
+ int end = mAdapter.getEnd();
+
+ if(dspLeftScreenEdge >= dspNextViewsRight || end == 0) return;
+
+ int dspModuloLeftScreenEdge = dspLeftScreenEdge % end;
+ int dspModuloNextViewsRight = dspNextViewsRight % end;
+ int dspOffsetLeftScreenEdge = dspLeftScreenEdge / end;
+ int dspOffsetNextViewsRight = dspNextViewsRight / end;
+
+ if(dspModuloLeftScreenEdge < 0) {
+ dspModuloLeftScreenEdge += end;
+ dspOffsetLeftScreenEdge -= 1;
+ }
+ if(dspModuloNextViewsRight < 0){
+ dspModuloNextViewsRight += end;
+ dspOffsetNextViewsRight -= 1;
+ }
+
+ View[] list;
+ if(dspModuloLeftScreenEdge > dspModuloNextViewsRight){
+ View[] list1,list2;
+ list1 = mAdapter.getViewsByRightSideRange(dspModuloLeftScreenEdge, end);
+ list2 = mAdapter.getViewsByRightSideRange(0, dspModuloNextViewsRight);
+ translateLayoutParams(list1, dspOffsetLeftScreenEdge);
+ translateLayoutParams(list2, dspOffsetNextViewsRight);
+
+ list = ToolBox.concatenateArray(list1, list2);
+ }
+ else{
+ list = mAdapter.getViewsByRightSideRange(dspModuloLeftScreenEdge, dspModuloNextViewsRight);
+ translateLayoutParams(list, dspOffsetLeftScreenEdge);
+ }
+
+ int dspMostLeft = dspNextViewsRight;
+ LayoutParams lp;
+ for(int i=0; i < list.length; i++){
+ lp = (LayoutParams) list[i].getLayoutParams();
+ if(lp.dspLeft < dspMostLeft) dspMostLeft = lp.dspLeft;
+ addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
+ }
+
+ if(list.length > 0){
+ layoutNewChildren(list);
+ }
+
+ mCurrentlyLayoutedViewsLeftEdgeDsp = dspMostLeft;
+ }
+
+ private void translateLayoutParams(View[] list,int offset){
+ if(offset == 0 || list.length == 0) return;
+
+ final int end = mAdapter.getEnd();
+ LayoutParams lp;
+
+ for(int i=0; i= 0) dspRightScreenEdge++; //to avoid problem with rounding of values
+
+ if(dspNextAddedViewsLeft >= dspRightScreenEdge || end == 0) return;
+
+ int dspModuloRightScreenEdge = dspRightScreenEdge % end;
+ int dspModuloNextAddedViewsLeft = dspNextAddedViewsLeft % end;
+ int dspOffsetRightScreenEdge = dspRightScreenEdge / end;
+ int dspOffsetNextAddedViewsLeft = dspNextAddedViewsLeft / end;
+
+ if(dspModuloRightScreenEdge < 0) {
+ dspModuloRightScreenEdge += end;
+ dspOffsetRightScreenEdge -= 1;
+ }
+ if(dspModuloNextAddedViewsLeft < 0) {
+ dspModuloNextAddedViewsLeft += end;
+ dspOffsetNextAddedViewsLeft -= 1;
+ }
+
+ View[] list;
+ if(dspModuloNextAddedViewsLeft > dspModuloRightScreenEdge){
+ View[] list1,list2;
+ list1 = mAdapter.getViewsByLeftSideRange(dspModuloNextAddedViewsLeft, end);
+ list2 = mAdapter.getViewsByLeftSideRange(0, dspModuloRightScreenEdge);
+ translateLayoutParams(list1, dspOffsetNextAddedViewsLeft);
+ translateLayoutParams(list2, dspOffsetRightScreenEdge);
+
+ list = ToolBox.concatenateArray(list1,list2);
+ }
+ else{
+ list = mAdapter.getViewsByLeftSideRange(dspModuloNextAddedViewsLeft, dspModuloRightScreenEdge);
+ translateLayoutParams(list, dspOffsetNextAddedViewsLeft);
+ }
+
+ int dspMostRight = 0;
+ LayoutParams lp;
+ for(int i=0; i < list.length; i++){
+ lp = (LayoutParams) list[i].getLayoutParams();
+ if(lp.getDspRight() > dspMostRight) dspMostRight = lp.getDspRight();
+ addViewInLayout(list[i], -1, list[i].getLayoutParams(), true);
+ }
+
+ if(list.length > 0){
+ layoutNewChildren(list);
+ }
+
+ mCurrentlyLayoutedViewsRightEdgeDsp = dspMostRight;
+ }
+
+ public void fling(int velocityX, int velocityY){
+ mTouchState = TOUCH_STATE_FLING;
+ final int x = getScrollX();
+ final int y = getScrollY();
+ final int bottomInPixels = dspToPx(mAdapter.getBottom()) + mDspHeightModulo;
+
+ mScroller.fling(x, y, velocityX, velocityY, Integer.MIN_VALUE,Integer.MAX_VALUE, 0, bottomInPixels - getHeight());
+
+ if(velocityX < 0) {
+ mScrollDirection = DIRECTION_LEFT;
+ }
+ else if(velocityX > 0) {
+ mScrollDirection = DIRECTION_RIGHT;
+ }
+
+ invalidate();
+ }
+
+ @Override
+ protected void scrollByDelta(int deltaX, int deltaY){
+ final int bottomInPixels = dspToPx(mAdapter.getBottom()) + mDspHeightModulo;
+ final int y = getScrollY() + deltaY;
+
+ if(y < 0 ) deltaY -= y;
+ else if(y > bottomInPixels - getHeight()) deltaY -= y - (bottomInPixels - getHeight());
+
+ if(deltaX < 0) {
+ mScrollDirection = DIRECTION_LEFT;
+ }
+ else {
+ mScrollDirection = DIRECTION_RIGHT;
+ }
+
+ scrollBy(deltaX, deltaY);
+ }
+
+}
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/contentbands/TileBase.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/contentbands/TileBase.java
similarity index 89%
rename from MAComponents/src/com/martinappl/components/ui/containers/contentbands/TileBase.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/contentbands/TileBase.java
index fa85c0b..2e2c2bf 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/contentbands/TileBase.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/contentbands/TileBase.java
@@ -1,98 +1,98 @@
-package com.martinappl.components.ui.containers.contentbands;
-
-/**
- * @author Martin Appl
- *
- * Base class for Content band datamodel. Extend to add data specific for your tiles and their behavior.
- * This class includes data needed for positioning of tiles inside container.
- */
-public class TileBase {
-
- private int id;
-
- private int x;
- private int y;
- private int z;
- private int width;
- private int height;
-
-
- public int getX(){
- return x;
- }
-
- public int getXRight(){
- return getX() + getWidth();
- }
-
- public int getY(){
- return y;
- }
-
- public int getZ(){
- return z;
- }
-
- public int getWidth(){
- return width;
- }
-
- public int getHeight(){
- return height;
- }
-
- public int getId(){
- return id;
- }
-
- public void setId(String id) {
- this.id = Integer.parseInt(id);
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public void setX(String x) {
- this.x = Integer.parseInt(x);
- }
-
- public void setX(int x) {
- this.x = x;
- }
-
- public void setY(String y) {
- this.y = Integer.parseInt(y);
- }
-
- public void setY(int y) {
- this.y = y;
- }
-
- public void setZ(String z) {
- this.z = Integer.parseInt(z);
- }
-
- public void setZ(int z) {
- this.z = z;
- }
-
- public void setWidth(String width) {
- this.width = Integer.parseInt(width);
- }
-
- public void setWidth(int width) {
- this.width = width;
- }
-
- public void setHeight(String height) {
- this.height = Integer.parseInt(height);
- }
-
- public void setHeight(int height) {
- this.height = height;
- }
-
-
-
-}
+package it.moondroid.coverflow.components.ui.containers.contentbands;
+
+/**
+ * @author Martin Appl
+ *
+ * Base class for Content band datamodel. Extend to add data specific for your tiles and their behavior.
+ * This class includes data needed for positioning of tiles inside container.
+ */
+public class TileBase {
+
+ private int id;
+
+ private int x;
+ private int y;
+ private int z;
+ private int width;
+ private int height;
+
+
+ public int getX(){
+ return x;
+ }
+
+ public int getXRight(){
+ return getX() + getWidth();
+ }
+
+ public int getY(){
+ return y;
+ }
+
+ public int getZ(){
+ return z;
+ }
+
+ public int getWidth(){
+ return width;
+ }
+
+ public int getHeight(){
+ return height;
+ }
+
+ public int getId(){
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = Integer.parseInt(id);
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public void setX(String x) {
+ this.x = Integer.parseInt(x);
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public void setY(String y) {
+ this.y = Integer.parseInt(y);
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ public void setZ(String z) {
+ this.z = Integer.parseInt(z);
+ }
+
+ public void setZ(int z) {
+ this.z = z;
+ }
+
+ public void setWidth(String width) {
+ this.width = Integer.parseInt(width);
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public void setHeight(String height) {
+ this.height = Integer.parseInt(height);
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+
+
+}
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/interfaces/IRemovableItemsAdapterComponent.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/interfaces/IRemovableItemsAdapterComponent.java
similarity index 81%
rename from MAComponents/src/com/martinappl/components/ui/containers/interfaces/IRemovableItemsAdapterComponent.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/interfaces/IRemovableItemsAdapterComponent.java
index ad13499..df29a8a 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/interfaces/IRemovableItemsAdapterComponent.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/interfaces/IRemovableItemsAdapterComponent.java
@@ -1,11 +1,11 @@
-package com.martinappl.components.ui.containers.interfaces;
-
-import android.view.View;
-
-public interface IRemovableItemsAdapterComponent {
- /**
- * Called when item is removed from component by user clicking on remove button
- * @return true, if you removed item from adapter manually in this step
- */
- boolean onItemRemove(int position, View view, Object item);
-}
+package it.moondroid.coverflow.components.ui.containers.interfaces;
+
+import android.view.View;
+
+public interface IRemovableItemsAdapterComponent {
+ /**
+ * Called when item is removed from component by user clicking on remove button
+ * @return true, if you removed item from adapter manually in this step
+ */
+ boolean onItemRemove(int position, View view, Object item);
+}
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/interfaces/IRemoveFromAdapter.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/interfaces/IRemoveFromAdapter.java
similarity index 55%
rename from MAComponents/src/com/martinappl/components/ui/containers/interfaces/IRemoveFromAdapter.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/interfaces/IRemoveFromAdapter.java
index 2530942..b16cc0b 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/interfaces/IRemoveFromAdapter.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/interfaces/IRemoveFromAdapter.java
@@ -1,6 +1,6 @@
-package com.martinappl.components.ui.containers.interfaces;
-
-
-public interface IRemoveFromAdapter{
- void removeItemFromAdapter(int position);
-}
+package it.moondroid.coverflow.components.ui.containers.interfaces;
+
+
+public interface IRemoveFromAdapter{
+ void removeItemFromAdapter(int position);
+}
diff --git a/MAComponents/src/com/martinappl/components/ui/containers/interfaces/IViewObserver.java b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/interfaces/IViewObserver.java
similarity index 75%
rename from MAComponents/src/com/martinappl/components/ui/containers/interfaces/IViewObserver.java
rename to lib/src/main/java/it/moondroid/coverflow/components/ui/containers/interfaces/IViewObserver.java
index 9328fce..eaf6224 100644
--- a/MAComponents/src/com/martinappl/components/ui/containers/interfaces/IViewObserver.java
+++ b/lib/src/main/java/it/moondroid/coverflow/components/ui/containers/interfaces/IViewObserver.java
@@ -1,11 +1,11 @@
-package com.martinappl.components.ui.containers.interfaces;
-
-import android.view.View;
-
-public interface IViewObserver {
- /**
- * @param v View which is getting removed
- * @param position View position in adapter
- */
- void onViewRemovedFromParent(View v, int position);
-}
+package it.moondroid.coverflow.components.ui.containers.interfaces;
+
+import android.view.View;
+
+public interface IViewObserver {
+ /**
+ * @param v View which is getting removed
+ * @param position View position in adapter
+ */
+ void onViewRemovedFromParent(View v, int position);
+}
diff --git a/MAComponents/res/drawable-mdpi/ico_delete_asset.png b/lib/src/main/res/drawable-mdpi/ico_delete_asset.png
similarity index 100%
rename from MAComponents/res/drawable-mdpi/ico_delete_asset.png
rename to lib/src/main/res/drawable-mdpi/ico_delete_asset.png
diff --git a/MAComponents/res/drawable-xhdpi/ico_delete_asset.png b/lib/src/main/res/drawable-xhdpi/ico_delete_asset.png
similarity index 100%
rename from MAComponents/res/drawable-xhdpi/ico_delete_asset.png
rename to lib/src/main/res/drawable-xhdpi/ico_delete_asset.png
diff --git a/MAComponents/res/values/attrs.xml b/lib/src/main/res/values/attrs.xml
similarity index 97%
rename from MAComponents/res/values/attrs.xml
rename to lib/src/main/res/values/attrs.xml
index 0431cb6..1c350f9 100644
--- a/MAComponents/res/values/attrs.xml
+++ b/lib/src/main/res/values/attrs.xml
@@ -1,40 +1,40 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/src/main/res/values/strings.xml b/lib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..5232723
--- /dev/null
+++ b/lib/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ CoverFlow
+
diff --git a/maven_push.gradle b/maven_push.gradle
new file mode 100644
index 0000000..b741e61
--- /dev/null
+++ b/maven_push.gradle
@@ -0,0 +1,92 @@
+apply plugin: 'maven'
+apply plugin: 'signing'
+
+def sonatypeRepositoryUrl
+if (isReleaseBuild()) {
+ println 'RELEASE BUILD'
+ sonatypeRepositoryUrl = hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
+ : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+} else {
+ println 'DEBUG BUILD'
+ sonatypeRepositoryUrl = hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
+ : "https://oss.sonatype.org/content/repositories/snapshots/"
+}
+
+def getRepositoryUsername() {
+ return hasProperty('nexusUsername') ? nexusUsername : ""
+}
+
+def getRepositoryPassword() {
+ return hasProperty('nexusPassword') ? nexusPassword : ""
+}
+
+afterEvaluate { project ->
+ uploadArchives {
+ repositories {
+ mavenDeployer {
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+ pom.artifactId = POM_ARTIFACT_ID
+
+ repository(url: sonatypeRepositoryUrl) {
+ 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.sourceFiles
+ }
+
+ task androidJavadocsJar(type: Jar) {
+ classifier = 'javadoc'
+ //basename = artifact_id
+ from androidJavadocs.destinationDir
+ }
+
+ task androidSourcesJar(type: Jar) {
+ classifier = 'sources'
+ //basename = artifact_id
+ from android.sourceSets.main.java.sourceFiles
+ }
+
+ artifacts {
+ //archives packageReleaseJar
+ archives androidSourcesJar
+ archives androidJavadocsJar
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..3a5a919
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':lib', ':app'