├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ ├── values │ │ │ │ ├── styles.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── strings.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── raizlabs │ │ │ └── android │ │ │ └── coreutils │ │ │ └── MainActivity.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── raizlabs │ │ └── android │ │ └── coreutils │ │ └── ApplicationTest.java ├── build.gradle └── proguard-rules.pro ├── settings.gradle ├── library ├── gradle.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── raizlabs │ │ │ └── coreutils │ │ │ ├── functions │ │ │ ├── Provider.java │ │ │ ├── Predicate.java │ │ │ ├── Delegate.java │ │ │ ├── DelegateSet.java │ │ │ └── PredicateGroup.java │ │ │ ├── listeners │ │ │ └── ProgressListener.java │ │ │ ├── util │ │ │ ├── Converter.java │ │ │ ├── observable │ │ │ │ ├── ObservableData.java │ │ │ │ └── lists │ │ │ │ │ ├── ListObserver.java │ │ │ │ │ ├── SimpleListObserverListener.java │ │ │ │ │ ├── ObservableList.java │ │ │ │ │ ├── ListObserverListener.java │ │ │ │ │ ├── SimpleListObserver.java │ │ │ │ │ └── ObservableListWrapper.java │ │ │ ├── Wrapper.java │ │ │ ├── CompatibilityUtils.java │ │ │ ├── ResourceUtils.java │ │ │ └── StringUtils.java │ │ │ ├── json │ │ │ ├── JSONArrayParserDelegate.java │ │ │ └── JSONHelper.java │ │ │ ├── events │ │ │ ├── IEvent.java │ │ │ ├── WeakRefEvent.java │ │ │ ├── SoftRefEvent.java │ │ │ ├── Event.java │ │ │ ├── WeakDelegateListObserverListener.java │ │ │ ├── HandlerEvent.java │ │ │ └── ReferenceEvent.java │ │ │ ├── collections │ │ │ ├── ListUtils.java │ │ │ ├── MappableSet.java │ │ │ ├── TransactionalHashSet.java │ │ │ ├── CategorizedList.java │ │ │ ├── CategorizedListFlattener.java │ │ │ ├── ProxyObservableList.java │ │ │ └── FilteredList.java │ │ │ ├── synchronization │ │ │ └── OneShotLock.java │ │ │ ├── concurrent │ │ │ ├── ConcurrencyUtils.java │ │ │ └── Prioritized.java │ │ │ ├── view │ │ │ ├── ViewCompatibility.java │ │ │ ├── animation │ │ │ │ └── AnimationListenerWrapper.java │ │ │ └── ViewUtils.java │ │ │ ├── app │ │ │ ├── FragmentStackManagerUtils.java │ │ │ └── FragmentStackManagerFragment.java │ │ │ ├── math │ │ │ └── MathUtils.java │ │ │ ├── logging │ │ │ └── Logger.java │ │ │ ├── threading │ │ │ └── ThreadingUtils.java │ │ │ ├── io │ │ │ └── IOUtils.java │ │ │ ├── widget │ │ │ └── ImageMixView.java │ │ │ └── graphics │ │ │ └── ImageFactory.java │ │ └── res │ │ └── values │ │ └── styles.xml ├── .settings │ ├── org.eclipse.jdt.core.prefs │ └── gradle │ │ ├── org.springsource.ide.eclipse.gradle.core.prefs │ │ └── org.springsource.ide.eclipse.gradle.refresh.prefs ├── project.properties ├── build.gradle └── proguard-project.txt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md ├── .gitignore ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=CoreUtils 2 | POM_PACKAGING=aar 3 | POM_ARTIFACT_ID=CoreUtils -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rightpoint/CoreUtils/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rightpoint/CoreUtils/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rightpoint/CoreUtils/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rightpoint/CoreUtils/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rightpoint/CoreUtils/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /library/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Feb 26 15:08:32 EST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CoreUtils 3 | MainActivity 4 | 5 | Hello world! 6 | Settings 7 | 8 | -------------------------------------------------------------------------------- /library/.settings/gradle/org.springsource.ide.eclipse.gradle.core.prefs: -------------------------------------------------------------------------------- 1 | #org.springsource.ide.eclipse.gradle.core.preferences.GradleProjectPreferences 2 | #Thu Oct 23 13:16:50 EDT 2014 3 | org.springsource.ide.eclipse.gradle.linkedresources= 4 | org.springsource.ide.eclipse.gradle.rootprojectloc=../../../.. 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoreUtils 2 | 3 | This library contains a hodgepodge of small utilities and helpers that we find useful across our projects and libraries. 4 | 5 | ## Including in your project 6 | 7 | ### Gradle 8 | 9 | By standard Gradle use: 10 | 11 | ``` 12 | dependencies { 13 | compile 'com.raizlabs:CoreUtils:1.1.7' 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /library/.settings/gradle/org.springsource.ide.eclipse.gradle.refresh.prefs: -------------------------------------------------------------------------------- 1 | #org.springsource.ide.eclipse.gradle.core.actions.GradleRefreshPreferences 2 | #Thu Oct 23 13:16:49 EDT 2014 3 | addResourceFilters=true 4 | afterTasks=afterEclipseImport; 5 | beforeTasks=cleanEclipse;eclipse; 6 | enableAfterTasks=true 7 | enableBeforeTasks=true 8 | enableDSLD=false 9 | useHierarchicalNames=false 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/functions/Provider.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.functions; 2 | 3 | /** 4 | * Interface which provides an object of a specific type. 5 | * 6 | * @param The type of object this interface provides. 7 | */ 8 | public interface Provider { 9 | 10 | /** 11 | * Obtains the provided object. 12 | * 13 | * @return The provided object. 14 | */ 15 | public T obtainProvided(); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/raizlabs/android/coreutils/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.android.coreutils; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/functions/Predicate.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.functions; 2 | 3 | /** 4 | * Interface for a predicate which evaluates a true or false value for a given item 5 | * 6 | * @param The type of item to evaluate. 7 | */ 8 | public interface Predicate { 9 | /** 10 | * Evaluates the given item. 11 | * 12 | * @param item The item to evaluate. 13 | * @return The true or false value for the given item. 14 | */ 15 | public boolean evaluate(T item); 16 | } 17 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/listeners/ProgressListener.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.listeners; 2 | 3 | /** 4 | * A listener which is called on progress updates. 5 | */ 6 | public interface ProgressListener { 7 | /** 8 | * Called when the progress is updated. 9 | * 10 | * @param currentProgress How far along we are. 11 | * @param maxProgress What the max value of progress will be, or negative if unknown 12 | */ 13 | public void onProgressUpdate(long currentProgress, long maxProgress); 14 | } 15 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/Converter.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util; 2 | 3 | /** 4 | * A {@link Converter} converts objects from one type to another. 5 | * @param The type to convert from. 6 | * @param The type to convert to. 7 | */ 8 | public interface Converter { 9 | /** 10 | * Called to convert the given object to the destination type. 11 | * @param from The object to convert from. 12 | * @return The converted object. 13 | */ 14 | To convert(From from); 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Eclipse project files 23 | .classpath 24 | .project 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Intellij project files 30 | *.iml 31 | *.ipr 32 | *.iws 33 | .idea/ 34 | crashlytics-build.properties 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/functions/Delegate.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.functions; 2 | 3 | /** 4 | * Interface for a delegate which can be executed on given parameters. 5 | * 6 | * @param The type of parameters that this {@link Delegate} executes 7 | * upon. 8 | */ 9 | public interface Delegate { 10 | /** 11 | * Executes this {@link Delegate} on the given parameters. 12 | * 13 | * @param params The parameters to the delegate. 14 | */ 15 | public void execute(Params params); 16 | } 17 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.raizlabs.coreutils" 9 | minSdkVersion 4 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile project(':library') 24 | } 25 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/json/JSONArrayParserDelegate.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.json; 2 | 3 | import org.json.JSONObject; 4 | 5 | /** 6 | * Delegate interface which parses an object from JSON during an array iteration. 7 | * 8 | * @param The type of object which will be parsed from the JSON 9 | */ 10 | public interface JSONArrayParserDelegate { 11 | /** 12 | * Called to parse an object from the given JSON 13 | * 14 | * @param json The {@link JSONObject} to parse 15 | * @return The object, or null if the data could not be parsed 16 | */ 17 | public T parseObject(JSONObject json); 18 | } -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-21 15 | android.library=true 16 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/observable/ObservableData.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util.observable; 2 | 3 | import com.raizlabs.coreutils.events.Event; 4 | 5 | /** 6 | * Interface which indicates that a class provides an {@link Event} which is 7 | * raised when its data changes. 8 | * 9 | * @param The type of data that will be sent as the arguments to the data 10 | * change event. In many cases, this may just be the class itself. 11 | */ 12 | public interface ObservableData { 13 | /** 14 | * @return The {@link Event} which will be raised when the data in this 15 | * object changes. 16 | */ 17 | public Event getDataChangedEvent(); 18 | } 19 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 4 9 | targetSdkVersion 21 10 | versionCode 1 11 | versionName VERSION_NAME 12 | } 13 | 14 | compileOptions { 15 | sourceCompatibility JavaVersion.VERSION_1_7 16 | targetCompatibility JavaVersion.VERSION_1_7 17 | } 18 | } 19 | 20 | dependencies { 21 | compile 'com.android.support:support-annotations:23.1.0' 22 | compile 'com.android.support:support-v4:23.1.0' 23 | } 24 | 25 | apply from: 'https://raw.githubusercontent.com/Raizlabs/maven-releases/bintrayScriptV2.0/bintray.gradle' -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/andrewgrosner/Documents/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /library/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /library/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 20 | 21 | 25 | 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/observable/lists/ListObserver.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util.observable.lists; 2 | 3 | /** 4 | * An interface which observes a list and maintains a set of listeners to be 5 | * notified of changes to the list. 6 | * 7 | * @param The type of items stored in the observed list. 8 | */ 9 | public interface ListObserver { 10 | /** 11 | * Adds a listener to be notified of changes to the list. 12 | * 13 | * @param listener The listener to add. 14 | */ 15 | public void addListener(ListObserverListener listener); 16 | 17 | /** 18 | * Removes a listener from being notified of changes to the list. 19 | * 20 | * @param listener The listener to remove. 21 | * @return True if the listener was removed, false if it hadn't been added. 22 | */ 23 | public boolean removeListener(ListObserverListener listener); 24 | } 25 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # The name of the version to be published 2 | VERSION_NAME= 1.1.7 3 | # The name of the Maven group being published to (groupId) 4 | # This is normally your package name minus the library name itself 5 | GROUP_NAME= com.raizlabs 6 | # The name of the artifact/library being published 7 | ARTIFACT_NAME= CoreUtils 8 | 9 | 10 | # The name of the user's organization to publish to 11 | BINTRAY_ORGANIZATION= raizlabs 12 | # The name of the repository to publish to within the organization 13 | BINTRAY_REPOSITORY= Libraries 14 | # Whether we should immediately publish to JCenter upon success 15 | BINTRAY_PUBLISH= true 16 | 17 | # The licensing information for Maven publishing 18 | LICENSE_NAME= The Apache Software License, Version 2.0 19 | LICENSE_URL= http://www.apache.org/licenses/LICENSE-2.0.txt 20 | 21 | # The source control and web urls for Maven publication 22 | GIT_URL = https://github.com/Raizlabs/CoreUtils.git 23 | SITE_URL = https://github.com/Raizlabs/CoreUtils -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/observable/lists/SimpleListObserverListener.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util.observable.lists; 2 | 3 | /** 4 | * A class which redirects all {@link ListObserverListener} calls into 5 | * {@link #onGenericChange(ListObserver)} so that any change fires one call. 6 | * This makes it function like the notifyDataSetChanged() pattern. 7 | * 8 | * @param The type of items stored in the observed list. 9 | */ 10 | public abstract class SimpleListObserverListener implements ListObserverListener { 11 | 12 | @Override 13 | public void onItemRangeChanged(ListObserver observer, int startPosition, int itemCount) { 14 | onGenericChange(observer); 15 | } 16 | 17 | @Override 18 | public void onItemRangeInserted(ListObserver observer, int startPosition, int itemCount) { 19 | onGenericChange(observer); 20 | } 21 | 22 | @Override 23 | public void onItemRangeRemoved(ListObserver observer, int startPosition, int itemCount) { 24 | onGenericChange(observer); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/functions/DelegateSet.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.functions; 2 | 3 | import com.raizlabs.coreutils.collections.TransactionalHashSet; 4 | 5 | /** 6 | * A class which contains a set of {@link Delegate}s and can easily call 7 | * {@link Delegate#execute(Object)} across all of them with given parameters. 8 | * It is also a {@link TransactionalHashSet} so it can block addition/removal 9 | * of delegates during it's execution or manually. 10 | * 11 | * @param The parameter type of the delegates. 12 | */ 13 | public class DelegateSet extends TransactionalHashSet> { 14 | 15 | private static final long serialVersionUID = 8162745188199411480L; 16 | 17 | /** 18 | * Calls {@link Delegate#execute(Object)} with the given parameters on all 19 | * of the contained delegates. 20 | * 21 | * @param params The parameters to pass to each delegate. 22 | */ 23 | public void execute(T params) { 24 | beginTransaction(); 25 | for (Delegate delegate : this) { 26 | delegate.execute(params); 27 | } 28 | endTransaction(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/Wrapper.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util; 2 | 3 | /** 4 | * Class which is a simple wrapper of an object of another type, allowing this 5 | * object to be used as a placeholder or property. 6 | * 7 | * @param The type to be wrapped. 8 | */ 9 | public class Wrapper { 10 | private T value; 11 | 12 | /** 13 | * Constructs a new, initially empty, {@link Wrapper}. 14 | */ 15 | public Wrapper() { 16 | } 17 | 18 | /** 19 | * Constructs a new {@link Wrapper} whose contents are initially set to the given 20 | * value. 21 | * 22 | * @param value The initial contents of the wrapper. 23 | */ 24 | public Wrapper(T value) { 25 | set(value); 26 | } 27 | 28 | /** 29 | * Sets the contents of this {@link Wrapper}. 30 | * 31 | * @param value The value to set. 32 | */ 33 | public void set(T value) { 34 | this.value = value; 35 | } 36 | 37 | /** 38 | * Gets the contents of this {@link Wrapper}. 39 | * 40 | * @return The contents of this {@link Wrapper}. 41 | */ 42 | public T get() { 43 | return value; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/CompatibilityUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util; 2 | 3 | import android.os.Build.VERSION; 4 | import android.os.Build.VERSION_CODES; 5 | 6 | public class CompatibilityUtils { 7 | /** 8 | * @return True if the current device supports the Honeycomb+ Animation APIs 9 | */ 10 | public static boolean supportsHoneycombAnimation() { 11 | return VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; 12 | } 13 | 14 | /** 15 | * @return True if the current device runs {@link VERSION_CODES#LOLLIPOP} or higher 16 | */ 17 | public static boolean isEqualToOrAboveLollipop() { 18 | return VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP; 19 | } 20 | 21 | /** 22 | * @return True if the current device runs {@link VERSION_CODES#KITKAT} or higher 23 | */ 24 | public static boolean isEqualToOrAboveKitKat() { 25 | return VERSION.SDK_INT >= VERSION_CODES.KITKAT; 26 | } 27 | 28 | /** 29 | * @return True if the current device runs {@link VERSION_CODES#ICE_CREAM_SANDWICH} or higher 30 | */ 31 | public static boolean isEqualToOrAboveICS() { 32 | return VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH; 33 | } 34 | } -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/events/IEvent.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.events; 2 | 3 | import com.raizlabs.coreutils.functions.Delegate; 4 | 5 | /** 6 | * Interface for a class which represents an event which notifies a set of 7 | * listeners when the event is raised. 8 | * 9 | * @param The parameter type of the event. 10 | */ 11 | public interface IEvent { 12 | 13 | /** 14 | * Subscribes the given listener to the event so it is called when the event 15 | * is raised. 16 | * 17 | * @param listener The listener to subscribe. 18 | */ 19 | public void addListener(Delegate listener); 20 | 21 | /** 22 | * Unsubscribes the given listener from the event so it is no longer called 23 | * when the event is raised. 24 | * 25 | * @param listener The listener to unsubscribe. 26 | * @return True if the listener was unsubscribed, false if it wasn't 27 | * subscribed. 28 | */ 29 | public boolean removeListener(Delegate listener); 30 | 31 | /** 32 | * Raises this event with the given parameters, notifying all subscribed 33 | * listeners. 34 | * 35 | * @param params The parameters to send to each listener. 36 | */ 37 | public void raiseEvent(T params); 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/raizlabs/android/coreutils/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.android.coreutils; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | 8 | 9 | public class MainActivity extends Activity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_main); 15 | } 16 | 17 | 18 | @Override 19 | public boolean onCreateOptionsMenu(Menu menu) { 20 | // Inflate the menu; this adds items to the action bar if it is present. 21 | getMenuInflater().inflate(R.menu.menu_main, menu); 22 | return true; 23 | } 24 | 25 | @Override 26 | public boolean onOptionsItemSelected(MenuItem item) { 27 | // Handle action bar item clicks here. The action bar will 28 | // automatically handle clicks on the Home/Up button, so long 29 | // as you specify a parent activity in AndroidManifest.xml. 30 | int id = item.getItemId(); 31 | 32 | //noinspection SimplifiableIfStatement 33 | if (id == R.id.action_settings) { 34 | return true; 35 | } 36 | 37 | return super.onOptionsItemSelected(item); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/collections/ListUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.collections; 2 | 3 | import com.raizlabs.coreutils.functions.Predicate; 4 | 5 | import java.util.List; 6 | import java.util.ListIterator; 7 | 8 | /** 9 | * Collection of utilities for use with {@link List}s. 10 | */ 11 | public class ListUtils { 12 | /** 13 | * Returns true if the given list is null or empty. 14 | * 15 | * @param list The list to check. 16 | * @return True if the list is null or empty. 17 | */ 18 | public static boolean isEmpty(List list) { 19 | return list == null || list.isEmpty(); 20 | } 21 | 22 | /** 23 | * Removes all items from the given list for which the given 24 | * {@link Predicate} returns false. 25 | * 26 | * @param list The list to filter. 27 | * @param predicate The predicate to run against list items. 28 | */ 29 | public static void filter(List list, Predicate predicate) { 30 | ListIterator iter = list.listIterator(); 31 | T currItem = null; 32 | 33 | while (iter.hasNext()) { 34 | currItem = iter.next(); 35 | if (!predicate.evaluate(currItem)) { 36 | iter.remove(); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/events/WeakRefEvent.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.events; 2 | 3 | import com.raizlabs.coreutils.functions.Delegate; 4 | 5 | import java.lang.ref.WeakReference; 6 | 7 | /** 8 | * Implementation of {@link ReferenceEvent} which uses weak references to its listeners. 9 | * 10 | * @param The parameter type of the event. 11 | */ 12 | public class WeakRefEvent extends ReferenceEvent> { 13 | 14 | @Override 15 | protected WeakDelegateReference createReference(Delegate listener) { 16 | return new WeakDelegateReference<>(listener); 17 | } 18 | 19 | static class WeakDelegateReference extends WeakReference> { 20 | 21 | public WeakDelegateReference(Delegate r) { 22 | super(r); 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | boolean superEquals = super.equals(o); 28 | 29 | if (!superEquals) { 30 | Delegate ref = get(); 31 | 32 | if (ref != null) { 33 | return ref.equals(o); 34 | } else if (o == null) { 35 | return true; 36 | } 37 | } 38 | 39 | return superEquals; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/synchronization/OneShotLock.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.synchronization; 2 | 3 | /** 4 | * A class which is locked until {@link #unlock()} is called. 5 | * Calling {@link #waitUntilUnlocked()} will block until this has 6 | * happened. You may synchronize on this object to lock its state. 7 | */ 8 | public class OneShotLock { 9 | private boolean unlocked; 10 | 11 | /** 12 | * @return True if {@link #unlock()} has been called 13 | */ 14 | public boolean isUnlocked() { 15 | return unlocked; 16 | } 17 | 18 | public OneShotLock() { 19 | unlocked = false; 20 | } 21 | 22 | /** 23 | * Unlocks this lock 24 | */ 25 | public void unlock() { 26 | synchronized (this) { 27 | unlocked = true; 28 | notifyAll(); 29 | } 30 | } 31 | 32 | /** 33 | * Blocks until this {@link OneShotLock} is unlocked via a call 34 | * to {@link #unlock()}. If this has already been unlocked, it 35 | * will return immediately. 36 | */ 37 | public void waitUntilUnlocked() { 38 | synchronized (this) { 39 | while (!unlocked) { 40 | try { 41 | wait(); 42 | } catch (InterruptedException e) { 43 | } 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/events/SoftRefEvent.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.events; 2 | 3 | import com.raizlabs.coreutils.functions.Delegate; 4 | 5 | import java.lang.ref.SoftReference; 6 | 7 | /** 8 | * Implementation of {@link ReferenceEvent} which uses soft references to its listeners. 9 | * 10 | * @param The parameter type of the event. 11 | */ 12 | public class SoftRefEvent extends ReferenceEvent> { 13 | 14 | public SoftRefEvent() { 15 | } 16 | 17 | @Override 18 | protected SoftDelegateReference createReference(Delegate listener) { 19 | return new SoftDelegateReference<>(listener); 20 | } 21 | 22 | static class SoftDelegateReference extends SoftReference> { 23 | 24 | public SoftDelegateReference(Delegate r) { 25 | super(r); 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | boolean superEquals = super.equals(o); 31 | 32 | if (!superEquals) { 33 | Delegate ref = get(); 34 | 35 | if (ref != null) { 36 | return ref.equals(o); 37 | } else if (o == null) { 38 | return true; 39 | } 40 | } 41 | 42 | return superEquals; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/observable/lists/ObservableList.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util.observable.lists; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Interface for a {@link List} implementation which is also observable via a 7 | * {@link ListObserver}. 8 | * 9 | * @param The type of data in the list. 10 | */ 11 | public interface ObservableList extends List { 12 | /** 13 | * Gets a {@link ListObserver} which can be used to be notified of changes 14 | * to the list data. 15 | * 16 | * @return The {@link ListObserver}. 17 | */ 18 | public ListObserver getListObserver(); 19 | 20 | /** 21 | * Begins a transaction. Changes will be visible immediately, but the data 22 | * changed events will not be raised until a call is made to 23 | * {@link #endTransaction()}. 24 | * 25 | * @throws IllegalStateException if a transaction is already running. 26 | * @see ObservableList#endTransaction() 27 | */ 28 | public void beginTransaction(); 29 | 30 | /** 31 | * Ends the current transaction. This will raise the data changed events 32 | * if any modifications have been made. 33 | * 34 | * @throws IllegalStateException if no transaction is currently running. 35 | * @see ObservableList#beginTransaction() 36 | */ 37 | public void endTransaction(); 38 | } 39 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/events/Event.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.events; 2 | 3 | import com.raizlabs.coreutils.functions.Delegate; 4 | import com.raizlabs.coreutils.functions.DelegateSet; 5 | 6 | /** 7 | * Simple implementation of {@link IEvent}. 8 | * 9 | * @param The parameter type of the event. 10 | */ 11 | public class Event implements IEvent { 12 | 13 | private DelegateSet listeners; 14 | 15 | public Event() { 16 | listeners = new DelegateSet<>(); 17 | } 18 | 19 | @Override 20 | public void addListener(Delegate listener) { 21 | listeners.add(listener); 22 | } 23 | 24 | @Override 25 | public boolean removeListener(Delegate listener) { 26 | return listeners.remove(listener); 27 | } 28 | 29 | @Override 30 | public void raiseEvent(T params) { 31 | performRaiseEvent(listeners, params); 32 | } 33 | 34 | /** 35 | * Called to actually raise the event. Subclasses may override this in order 36 | * to perform custom logic, but should be sure to execute the event across 37 | * the given listeners. 38 | * 39 | * @param listeners A set of listeners which need to be notified. 40 | * @param params The parameters to be sent to each listener. 41 | */ 42 | protected void performRaiseEvent(DelegateSet listeners, T params) { 43 | listeners.execute(params); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/concurrent/ConcurrencyUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.concurrent; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | 5 | /** 6 | * Class containing some helper methods related to concurrency. 7 | */ 8 | public class ConcurrencyUtils { 9 | /** 10 | * Puts the given value in the given map for the given key if no mapping exists, 11 | * and returns the new value. 12 | * 13 | * @param map The map to store the value in. 14 | * @param key The key in the map to store the value in. 15 | * @param absentValue The value to put if no mapping exists. 16 | * @return The value that is now stored in the map. This will be absentValue if 17 | * no mapping existed and the absentValue was stored, or the existing value if 18 | * one existed. 19 | */ 20 | public static V putIfAbsent(ConcurrentHashMap map, K key, V absentValue) { 21 | // First quickly try to get the value 22 | V value = map.get(key); 23 | // If we failed to retrieve one, try to put the absentValue 24 | if (value == null) { 25 | value = map.putIfAbsent(key, absentValue); 26 | // If there was not an old mapping, it is now mapped to absent value 27 | if (value == null) { 28 | value = absentValue; 29 | } 30 | } 31 | // Return the current mapping. 32 | return value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/events/WeakDelegateListObserverListener.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.events; 2 | 3 | import com.raizlabs.coreutils.util.observable.lists.ListObserver; 4 | import com.raizlabs.coreutils.util.observable.lists.ListObserverListener; 5 | 6 | import java.lang.ref.WeakReference; 7 | 8 | public abstract class WeakDelegateListObserverListener implements ListObserverListener { 9 | 10 | private WeakReference weakRef; 11 | 12 | /** 13 | * Constructs a new listener which weakly references the given delegate. 14 | * @param delegate The delegate to weakly reference. 15 | */ 16 | public WeakDelegateListObserverListener(Delegate delegate) { 17 | this.weakRef = new WeakReference(delegate); 18 | } 19 | 20 | /** 21 | * Called to attempt to retrieve the delegate. If it can't be reached, we 22 | * will attempt to remove ourselves from the given observer. 23 | * @param observer An observer to attempt to remove this listener from if 24 | * we can't reach the delegate. 25 | * @return The delegate, or null if it could not be reached. 26 | */ 27 | protected Delegate getDelegate(ListObserver observer) { 28 | Delegate proxy = weakRef.get(); 29 | if (proxy != null) { 30 | return proxy; 31 | } else { 32 | if (observer != null) { 33 | observer.removeListener(this); 34 | } 35 | return null; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/ResourceUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.content.res.Resources.Theme; 6 | import android.os.Build; 7 | 8 | public class ResourceUtils { 9 | 10 | /** 11 | * Retrieves the color for the given ID from the specified {@link Context} and, if applicable, its associated 12 | * {@link Theme}. 13 | * @see #getColor(Resources, int, Theme) 14 | * @param context The context to retrieve the color from. 15 | * @param id The ID of the color to retrieve. 16 | * @return The retrieved color. 17 | */ 18 | public static int getColor(Context context, int id) { 19 | return getColor(context.getResources(), id, context.getTheme()); 20 | } 21 | 22 | /** 23 | * Retrieves the color for the given ID from the specified {@link Resources} and {@link Theme}. This will look up 24 | * using the theme if the system API is available, otherwise it will just use the default. 25 | * @param resources The resources to retrieve the color from. 26 | * @param id The ID of the color to retrieve. 27 | * @param theme The theme to use to retrieve the color. 28 | * @return The retrieved color. 29 | */ 30 | public static int getColor(Resources resources, int id, Theme theme) { 31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 32 | return resources.getColor(id, theme); 33 | } else { 34 | return resources.getColor(id); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/concurrent/Prioritized.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.concurrent; 2 | 3 | import java.util.Comparator; 4 | 5 | /** 6 | * Indicates that a class has a priority. 7 | */ 8 | public interface Prioritized { 9 | /** 10 | * Some predefined priority values. 11 | */ 12 | public static final class Priority { 13 | public static final int EXTREMELY_LOW = -1000; 14 | public static final int BACKGROUND = -300; 15 | public static final int LESS_FAVORABLE = -50; 16 | public static final int NORMAL = 0; 17 | public static final int MORE_FAVORABLE = 50; 18 | public static final int FOREGROUND = 300; 19 | public static final int IMMEDIATE = 1000; 20 | } 21 | 22 | /** 23 | * {@link Comparator} implementation which will put the higher priority 24 | * {@link Prioritized} objects first. 25 | */ 26 | public static final Comparator COMPARATOR_HIGH_FIRST = new Comparator() { 27 | public int compare(Prioritized lhs, Prioritized rhs) { 28 | final int lhp = lhs.getPriority(); 29 | final int rhp = rhs.getPriority(); 30 | 31 | if (lhp == rhp) return 0; 32 | if (lhp > rhp) return -1; 33 | else return 1; 34 | } 35 | }; 36 | 37 | /** 38 | * {@link Comparator} implementation which will put the lower priority 39 | * {@link Prioritized} objects first. 40 | */ 41 | public static final Comparator COMPARATOR_LOW_FIRST = new Comparator() { 42 | public int compare(Prioritized lhs, Prioritized rhs) { 43 | return -COMPARATOR_HIGH_FIRST.compare(lhs, rhs); 44 | } 45 | }; 46 | 47 | /** 48 | * Gets the priority of this object. 49 | * 50 | * @return The priority of this object. 51 | */ 52 | public int getPriority(); 53 | } 54 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/view/ViewCompatibility.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.view; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.graphics.drawable.Drawable; 5 | import android.os.Build; 6 | import android.view.View; 7 | import android.view.ViewTreeObserver; 8 | import android.view.ViewTreeObserver.OnGlobalLayoutListener; 9 | 10 | /** 11 | * Compatibility helpers for view related tasks. Handles using API level 12 | * appropriate methods based on the run time SDK. 13 | */ 14 | public class ViewCompatibility { 15 | 16 | /** 17 | * Removes the given {@link OnGlobalLayoutListener} from the given 18 | * {@link ViewTreeObserver} using the most appropriate method in the 19 | * current SDK. 20 | * 21 | * @param observer The view tree observer to remove the listener from. 22 | * @param listener The listener to remove. 23 | */ 24 | @SuppressLint("NewApi") 25 | @SuppressWarnings("deprecation") 26 | public static void removeGlobalOnLayoutListener(ViewTreeObserver observer, OnGlobalLayoutListener listener) { 27 | if (Build.VERSION.SDK_INT >= 16) { 28 | observer.removeOnGlobalLayoutListener(listener); 29 | } else { 30 | observer.removeGlobalOnLayoutListener(listener); 31 | } 32 | } 33 | 34 | /** 35 | * Sets the given {@link Drawable} as the background of the given 36 | * {@link View}. 37 | * 38 | * @param view The view to set the background of. 39 | * @param background The drawable to set as the background. 40 | */ 41 | @SuppressLint("NewApi") 42 | @SuppressWarnings("deprecation") 43 | public static void setViewBackground(View view, Drawable background) { 44 | if (Build.VERSION.SDK_INT >= 16) { 45 | view.setBackground(background); 46 | } else { 47 | view.setBackgroundDrawable(background); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/app/FragmentStackManagerUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.app; 2 | 3 | import android.support.v4.app.FragmentActivity; 4 | import android.support.v4.app.FragmentManager; 5 | 6 | public class FragmentStackManagerUtils { 7 | 8 | /** 9 | * Obtains a {@link FragmentStackManagerFragment} for the given container 10 | * via the {@link FragmentManager} in the given activity. 11 | * This will obtain any previous manager that still exists or create one 12 | * if one can't be found. 13 | * @param activity The {@link FragmentActivity} whose {@link FragmentManager} 14 | * should be used 15 | * @param containerID The resource ID of the container the manager will be 16 | * bound to. 17 | * @return The manager bound to the given container 18 | */ 19 | public static FragmentStackManagerFragment getFragmentStackManager(FragmentActivity activity, int containerID) { 20 | return getFragmentStackManager(activity.getSupportFragmentManager(), containerID); 21 | } 22 | 23 | public static FragmentStackManagerFragment getFragmentStackManager(FragmentManager manager, int containerID) { 24 | final String tag = getFragmentStackManagerTag(containerID); 25 | 26 | // Get any FragmentStackManager if one exists 27 | FragmentStackManagerFragment stackManager = (FragmentStackManagerFragment) 28 | manager.findFragmentByTag(tag); 29 | 30 | // Create and store one if it does not 31 | if (stackManager == null) { 32 | stackManager = FragmentStackManagerFragment.newInstance(containerID); 33 | 34 | manager.beginTransaction() 35 | .add(stackManager, tag) 36 | .commit(); 37 | } 38 | 39 | return stackManager; 40 | } 41 | 42 | private static String getFragmentStackManagerTag(int containerID) { 43 | return String.format("StackManager%d", containerID); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/observable/lists/ListObserverListener.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util.observable.lists; 2 | 3 | /** 4 | * Interface which listens to {@link ListObserver} changes. 5 | * 6 | * @param The type of items stored in the observed list. 7 | */ 8 | public interface ListObserverListener { 9 | 10 | /** 11 | * Called to indicate that the items in a given range were changed. 12 | * 13 | * @param observer The {@link ListObserver} which notified of this change. 14 | * @param startPosition The position of the first element that changed. 15 | * @param itemCount How many sequential items were changed. 16 | */ 17 | public void onItemRangeChanged(ListObserver observer, int startPosition, int itemCount); 18 | 19 | /** 20 | * Called to indicate that the items were inserted at a given range. 21 | * 22 | * @param observer The {@link ListObserver} which notified of this change. 23 | * @param startPosition The position of the first element that was inserted. 24 | * @param itemCount How many sequential items were inserted. 25 | */ 26 | public void onItemRangeInserted(ListObserver observer, int startPosition, int itemCount); 27 | 28 | /** 29 | * Called to indicate that the items were removed at a given range. 30 | * 31 | * @param observer The {@link ListObserver} which notified of this change. 32 | * @param startPosition The position of the first element that was removed. 33 | * @param itemCount How many sequential items were inserted. 34 | */ 35 | public void onItemRangeRemoved(ListObserver observer, int startPosition, int itemCount); 36 | 37 | /** 38 | * Called to indicate that a generic change happened which changed the list 39 | * contents. 40 | * 41 | * @param observer The {@link ListObserver} which notified of this change. 42 | */ 43 | public void onGenericChange(ListObserver observer); 44 | } 45 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/events/HandlerEvent.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.events; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import com.raizlabs.coreutils.functions.DelegateSet; 7 | import com.raizlabs.coreutils.threading.ThreadingUtils; 8 | 9 | /** 10 | * An {@link Event} which calls all listeners on a {@link Handler} when it is 11 | * raised instead of doing so on the calling thread. If the event is raised on 12 | * the thread it is bound to, it will be called inline. 13 | * 14 | * @param The parameter type of the event. 15 | */ 16 | public class HandlerEvent extends Event { 17 | 18 | private Handler handler; 19 | 20 | /** 21 | * Constructs a {@link HandlerEvent} which will be dispatched on the 22 | * current thread. Throws an exception if this thread is not a looper 23 | * thread. 24 | * 25 | * @throws RuntimeException if called from a non-looper thread. 26 | */ 27 | public HandlerEvent() { 28 | super(); 29 | Looper looper = Looper.myLooper(); 30 | if (looper != null) { 31 | this.handler = new Handler(looper); 32 | } else { 33 | throw new RuntimeException("Can't create a default " + getClass().getSimpleName() + " constructor from a non-looper thread."); 34 | } 35 | } 36 | 37 | /** 38 | * Constructs a {@link HandlerEvent} which will be dispatched via the given 39 | * {@link Handler}. 40 | * 41 | * @param handler The handler to dispatch the event on. 42 | */ 43 | public HandlerEvent(Handler handler) { 44 | super(); 45 | this.handler = handler; 46 | } 47 | 48 | @Override 49 | protected void performRaiseEvent(final DelegateSet listeners, final T params) { 50 | ThreadingUtils.runOnHandler(new Runnable() { 51 | @Override 52 | public void run() { 53 | listeners.execute(params); 54 | } 55 | }, handler); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util; 2 | 3 | /** 4 | * Collection of utilities for use with {@link String}s. 5 | */ 6 | public class StringUtils { 7 | 8 | /** 9 | * Joins the given set of strings, placing the given delimiter between each. 10 | * 11 | * @param delimiter The string to place between each item. 12 | * @param items The set of strings to join together. 13 | * @return The joined {@link String}. 14 | */ 15 | public static String join(String delimiter, Iterable items) { 16 | StringBuilder builder = new StringBuilder(); 17 | 18 | boolean isFirst = true; 19 | for (String str : items) { 20 | if (!isFirst) builder.append(delimiter); 21 | builder.append(str); 22 | isFirst = false; 23 | } 24 | 25 | return builder.toString(); 26 | } 27 | 28 | /** 29 | * Returns true if any of the the given strings are null, the empty string, or the 30 | * string "null". 31 | * 32 | * @param strs The list of strings to check. 33 | * @return True if any string is null or empty. 34 | */ 35 | public static boolean isNullOrEmpty(String... strs) { 36 | for (String str : strs) { 37 | if (str == null || str.equals("") || str.equals("null")) { 38 | return true; 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | /** 45 | * Returns true if all of the the given strings are not null, the empty string, or the 46 | * string "null". 47 | * 48 | * @param strs The list of strings to check. 49 | * @return True if all the strings are not null or empty. 50 | */ 51 | public static boolean isNotNullOrEmpty(String... strs) { 52 | for (String str : strs) { 53 | if (str == null || str.equals("") || str.equals("null")) { 54 | return false; 55 | } 56 | } 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/events/ReferenceEvent.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.events; 2 | 3 | import com.raizlabs.coreutils.collections.TransactionalHashSet; 4 | import com.raizlabs.coreutils.functions.Delegate; 5 | import com.raizlabs.coreutils.functions.DelegateSet; 6 | 7 | import java.lang.ref.Reference; 8 | 9 | /** 10 | * Base reference event class that subclasses share logic for. 11 | */ 12 | abstract class ReferenceEvent>> implements IEvent { 13 | 14 | private TransactionalHashSet listeners; 15 | 16 | public ReferenceEvent() { 17 | listeners = new TransactionalHashSet<>(); 18 | } 19 | 20 | @Override 21 | public void addListener(Delegate listener) { 22 | listeners.add(createReference(listener)); 23 | } 24 | 25 | @Override 26 | public boolean removeListener(Delegate listener) { 27 | return listeners.remove(createReference(listener)); 28 | } 29 | 30 | protected abstract ReferenceClass createReference(Delegate listener); 31 | 32 | @Override 33 | public void raiseEvent(T params) { 34 | listeners.beginTransaction(); 35 | for (ReferenceClass reference : listeners) { 36 | Delegate listener = reference.get(); 37 | 38 | if (listener != null) { 39 | listener.execute(params); 40 | } else { 41 | listeners.remove(reference); 42 | } 43 | } 44 | listeners.endTransaction(); 45 | } 46 | 47 | /** 48 | * Called to actually raise the event. Subclasses may override this in order 49 | * to perform custom logic, but should be sure to execute the event across 50 | * the given listeners. 51 | * 52 | * @param listeners A set of listeners which need to be notified. 53 | * @param params The parameters to be sent to each listener. 54 | */ 55 | protected void performRaiseEvent(DelegateSet listeners, T params) { 56 | listeners.execute(params); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/collections/MappableSet.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.collections; 2 | 3 | import com.raizlabs.coreutils.functions.Delegate; 4 | 5 | 6 | /** 7 | * A class which contains a set of items and can perform an actions across all 8 | * items in the set. 9 | * 10 | * @param The type of item stored in the set. 11 | */ 12 | public class MappableSet { 13 | 14 | private TransactionalHashSet members; 15 | 16 | protected Iterable getMembers() { 17 | return members; 18 | } 19 | 20 | /** 21 | * Creates a new empty {@link MappableSet}. 22 | */ 23 | public MappableSet() { 24 | members = new TransactionalHashSet(); 25 | } 26 | 27 | /** 28 | * Adds the given item to the set. 29 | * 30 | * @param item The item to add. 31 | */ 32 | public void add(E item) { 33 | members.add(item); 34 | } 35 | 36 | /** 37 | * Removes the given item from the set. 38 | * 39 | * @param item The item to remove. 40 | * @return True if the item was removed, false if it wasn't found in the 41 | * set. 42 | */ 43 | public boolean remove(E item) { 44 | return members.remove(item); 45 | } 46 | 47 | /** 48 | * Removes all items from this set. 49 | */ 50 | public void clear() { 51 | members.clear(); 52 | } 53 | 54 | /** 55 | * Returns true if the given item is found in the set. 56 | * 57 | * @param item The item to look for. 58 | * @return True if the item was found, false if it is not. 59 | */ 60 | public boolean contains(E item) { 61 | return members.contains(item); 62 | } 63 | 64 | /** 65 | * @return The number of items in this set. 66 | */ 67 | public int size() { 68 | return members.size(); 69 | } 70 | 71 | /** 72 | * Calls the given {@link Delegate} on all items in the set. Items may be 73 | * added or removed during this call, but changes may not be reflected 74 | * until this completes. 75 | * 76 | * @param function The {@link Delegate} to call for each item. 77 | */ 78 | public void map(Delegate function) { 79 | beginTransaction(); 80 | for (T member : members) { 81 | function.execute(member); 82 | } 83 | endTransaction(); 84 | } 85 | 86 | public void beginTransaction() { 87 | members.beginTransaction(); 88 | } 89 | 90 | public void endTransaction() { 91 | members.endTransaction(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/math/MathUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.math; 2 | 3 | 4 | /** 5 | * A class of math utility functions 6 | */ 7 | public class MathUtils { 8 | /** 9 | * Returns a linearly interpolated value t percent of the way between the 10 | * given start and end values. 11 | * 12 | * @param start The start value 13 | * @param end The end value 14 | * @param t The percentage along the way to end (0 being the start, 1 being 15 | * the end) 16 | * @return The linearly interpolated value 17 | */ 18 | public static int lerp(int start, int end, float t) { 19 | return (int) (start + (end - start) * t); 20 | } 21 | 22 | /** 23 | * Returns a linearly interpolated value t percent of the way between the 24 | * given start and end values. 25 | * 26 | * @param start The start value 27 | * @param end The end value 28 | * @param t The percentage along the way to end (0 being the start, 1 being 29 | * the end) 30 | * @return The linearly interpolated value 31 | */ 32 | public static float lerp(float start, float end, float t) { 33 | return start + (end - start) * t; 34 | } 35 | 36 | /** 37 | * Returns a value between 0 and 1 which represents how far along the start 38 | * and end values the given value is. For example, normalize(0, 100, 50) would 39 | * return 0.5 as 50 is halfway between 0 and 100. 40 | *

41 | * Note that values larger than end will produce numbers larger than 1 and 42 | * values smaller than start will produce negative numbers. 43 | *

44 | * Inverse of {@link #lerp(float, float, float)} 45 | * 46 | * @param start The start value 47 | * @param end The end value 48 | * @param value The value to normalize 49 | * @return The value normalized between start and end 50 | */ 51 | public static float normalize(float start, float end, float value) { 52 | return (value - start) / (end - start); 53 | } 54 | 55 | /** 56 | * Clamps the given value between the two end points. 57 | * 58 | * @param value The value to clamp. 59 | * @param min The minimum allowed value. 60 | * @param max The maximum allowed value. 61 | * @return The clamped value. 62 | */ 63 | public static float clamp(float value, float min, float max) { 64 | return java.lang.Math.min(max, java.lang.Math.max(min, value)); 65 | } 66 | 67 | public static float distanceSquared(float x1, float x2, float y1, float y2) { 68 | final float deltaX = x2 - x1; 69 | final float deltaY = y2 - y1; 70 | return (deltaX * deltaX) + (deltaY * deltaY); 71 | } 72 | 73 | public static float distance(float x1, float x2, float y1, float y2) { 74 | final float deltaX = x2 - x1; 75 | final float deltaY = y2 - y1; 76 | return (float) java.lang.Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/functions/PredicateGroup.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.functions; 2 | 3 | import java.util.HashSet; 4 | 5 | /** 6 | * Groups predicates together and enables them to act as one large {@link Predicate} 7 | * 8 | * @param the type of item associated with this {@link PredicateGroup} 9 | */ 10 | public class 11 | PredicateGroup implements Predicate { 12 | 13 | private HashSet> predicateSet = new HashSet<>(); 14 | 15 | private boolean allPredicatesRequired = false; 16 | 17 | @SafeVarargs 18 | public PredicateGroup(boolean allPredicatesRequired, Predicate... optionalPredicates) { 19 | for (Predicate predicate : optionalPredicates) { 20 | addPredicate(predicate); 21 | } 22 | 23 | setAllPredicatesRequired(allPredicatesRequired); 24 | } 25 | 26 | /** 27 | * @param allPredicatesRequired if true, all contained {@link Predicate} must be true. If false, 28 | * at least one {@link Predicate} can be evaluated to true without failing 29 | */ 30 | public void setAllPredicatesRequired(boolean allPredicatesRequired) { 31 | this.allPredicatesRequired = allPredicatesRequired; 32 | } 33 | 34 | /** 35 | * Adds {@link Predicate} to group and indicated whether {@link Predicate} is required or not 36 | * 37 | * @param predicate {@link Predicate} to add to group 38 | */ 39 | public void addPredicate(Predicate predicate) { 40 | predicateSet.add(predicate); 41 | } 42 | 43 | /** 44 | * Removes {@link Predicate} from group 45 | * 46 | * @param predicate {@link Predicate} to remove from group 47 | */ 48 | public void removePredicate(Predicate predicate) { 49 | predicateSet.remove(predicate); 50 | } 51 | 52 | /** 53 | * Checks to see if group contains the indicated {@link Predicate} 54 | * 55 | * @param predicate {@link Predicate} to check for 56 | * @return true if group contains the indicated {@link Predicate}, false otherwise 57 | */ 58 | public boolean contains(Predicate predicate) { 59 | return predicateSet.contains(predicate); 60 | } 61 | 62 | /** 63 | * Evaluates the group of predicates using an & or | strategy depending on the allPredicatesRequired 64 | * variable 65 | * 66 | * @param item The item to evaluate. 67 | * @return true if the item fulfills the predicates, false otherwise 68 | */ 69 | @Override 70 | public boolean evaluate(Data item) { 71 | if (allPredicatesRequired) { 72 | for (Predicate predicate : predicateSet) { 73 | if (predicate != null) { 74 | if (!predicate.evaluate(item)) { 75 | return false; 76 | } 77 | } 78 | } 79 | return true; 80 | 81 | } else { 82 | for (Predicate predicate : predicateSet) { 83 | if (predicate != null) { 84 | if (predicate.evaluate(item)) { 85 | return true; 86 | } 87 | } 88 | } 89 | return false; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/observable/lists/SimpleListObserver.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util.observable.lists; 2 | 3 | import com.raizlabs.coreutils.collections.MappableSet; 4 | import com.raizlabs.coreutils.functions.Delegate; 5 | 6 | /** 7 | * Simple implementation of a {@link ListObserver}. 8 | */ 9 | public class SimpleListObserver implements ListObserver { 10 | 11 | private MappableSet> listeners; 12 | 13 | public SimpleListObserver() { 14 | listeners = new MappableSet>(); 15 | } 16 | 17 | @Override 18 | public void addListener(ListObserverListener listener) { 19 | listeners.add(listener); 20 | } 21 | 22 | @Override 23 | public boolean removeListener(ListObserverListener listener) { 24 | return listeners.remove(listener); 25 | } 26 | 27 | /** 28 | * Call to notify listeners that the items in a given range were changed. 29 | * 30 | * @param startPosition The position of the first element that changed. 31 | * @param itemCount How many sequential items were changed. 32 | */ 33 | public void notifyItemRangeChanged(final int startPosition, final int itemCount) { 34 | mapListeners(new Delegate>() { 35 | @Override 36 | public void execute(ListObserverListener listener) { 37 | listener.onItemRangeChanged(SimpleListObserver.this, startPosition, itemCount); 38 | } 39 | }); 40 | } 41 | 42 | /** 43 | * Call to notify listeners that items were inserted at a given range. 44 | * 45 | * @param startPosition The position of the first element that was inserted. 46 | * @param itemCount How many sequential items were inserted. 47 | */ 48 | public void notifyItemRangeInserted(final int startPosition, final int itemCount) { 49 | mapListeners(new Delegate>() { 50 | @Override 51 | public void execute(ListObserverListener listener) { 52 | listener.onItemRangeInserted(SimpleListObserver.this, startPosition, itemCount); 53 | } 54 | }); 55 | } 56 | 57 | /** 58 | * Call to notify listeners that items were removed at a given range. 59 | * 60 | * @param startPosition The position of the first element that was removed. 61 | * @param itemCount How many sequential items were removed. 62 | */ 63 | public void notifyItemRangeRemoved(final int startPosition, final int itemCount) { 64 | mapListeners(new Delegate>() { 65 | @Override 66 | public void execute(ListObserverListener listener) { 67 | listener.onItemRangeRemoved(SimpleListObserver.this, startPosition, itemCount); 68 | } 69 | }); 70 | } 71 | 72 | /** 73 | * Call to notify listeners that a generic change happened which changed 74 | * the list contents. 75 | */ 76 | public void notifyGenericChange() { 77 | mapListeners(genericChangeDelegate); 78 | } 79 | 80 | private void mapListeners(Delegate> function) { 81 | listeners.beginTransaction(); 82 | listeners.map(function); 83 | listeners.endTransaction(); 84 | } 85 | 86 | private final Delegate> genericChangeDelegate = new Delegate>() { 87 | @Override 88 | public void execute(ListObserverListener listener) { 89 | listener.onGenericChange(SimpleListObserver.this); 90 | } 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/view/animation/AnimationListenerWrapper.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.view.animation; 2 | 3 | import android.view.animation.Animation; 4 | import android.view.animation.Animation.AnimationListener; 5 | 6 | import java.util.HashSet; 7 | 8 | /** 9 | * {@link AnimationListener} implementation which manages a set of 10 | * other {@link AnimationListener}s. When events are called on this 11 | * {@link AnimationListenerWrapper}, each of the managed 12 | * {@link AnimationListener}s are called appropriately. 13 | */ 14 | public class AnimationListenerWrapper implements AnimationListener { 15 | private HashSet listeners; 16 | 17 | /** 18 | * Constructs an empty {@link AnimationListenerWrapper}. 19 | */ 20 | public AnimationListenerWrapper() { 21 | listeners = new HashSet(); 22 | } 23 | 24 | /** 25 | * Constructs an {@link AnimationListenerWrapper} and subscribes the 26 | * given {@link AnimationListener}. 27 | * 28 | * @param listener An {@link AnimationListener} to subscribe to events. 29 | */ 30 | public AnimationListenerWrapper(AnimationListener listener) { 31 | this(); 32 | addListener(listener); 33 | } 34 | 35 | /** 36 | * Constructs an {@link AnimationListenerWrapper} and subscribes all 37 | * the given {@link AnimationListener}s to events. 38 | * 39 | * @param listeners 40 | */ 41 | public AnimationListenerWrapper(Iterable listeners) { 42 | this(); 43 | synchronized (listeners) { 44 | for (AnimationListener listener : listeners) { 45 | this.listeners.add(listener); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * Adds an {@link AnimationListener} to be called on events. 52 | * 53 | * @param listener The {@link AnimationListener} to subscribe. 54 | */ 55 | public void addListener(AnimationListener listener) { 56 | if (listener != null) { 57 | synchronized (listeners) { 58 | listeners.add(listener); 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * Removes an {@link AnimationListener} so it will not receive 65 | * calls on future events. 66 | * 67 | * @param listener The {@link AnimationListener} to remove. 68 | * @return True if the listener was removed, false if it wasn't 69 | * found or was null. 70 | */ 71 | public boolean removeListener(AnimationListener listener) { 72 | if (listener != null) { 73 | synchronized (listeners) { 74 | return listeners.remove(listener); 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | @Override 81 | public void onAnimationEnd(Animation animation) { 82 | synchronized (listeners) { 83 | for (AnimationListener listener : listeners) { 84 | listener.onAnimationEnd(animation); 85 | } 86 | } 87 | } 88 | 89 | @Override 90 | public void onAnimationRepeat(Animation animation) { 91 | synchronized (listeners) { 92 | for (AnimationListener listener : listeners) { 93 | listener.onAnimationRepeat(animation); 94 | } 95 | } 96 | } 97 | 98 | @Override 99 | public void onAnimationStart(Animation animation) { 100 | synchronized (listeners) { 101 | for (AnimationListener listener : listeners) { 102 | listener.onAnimationStart(animation); 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/logging/Logger.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.logging; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import android.util.Log; 6 | 7 | /** 8 | * Class which logs to the built in Android {@link Log}, but provides a 9 | * mechanism to disable logging at different levels. 10 | */ 11 | public class Logger { 12 | 13 | private static int logFlags = LogLevel.WARNINGS | LogLevel.ERRORS; 14 | 15 | /** 16 | * Sets the log levels which will be logged to the Android {@link Log}. 17 | * 18 | * @param logLevel A bitmask of the desired levels, using values defined in 19 | * {@link LogLevel}. 20 | */ 21 | public static void setLogLevel(int logLevel) { 22 | logFlags = logLevel; 23 | } 24 | 25 | public static void v(String tag, String msg) { 26 | if ((logFlags & LogLevel.VERBOSE) > 0) { 27 | Log.v(tag, msg); 28 | } 29 | } 30 | 31 | public static void v(String tag, String msg, Throwable tr) { 32 | if ((logFlags & LogLevel.VERBOSE) > 0) { 33 | Log.v(tag, msg, tr); 34 | } 35 | } 36 | 37 | public static void d(String tag, String msg) { 38 | if ((logFlags & LogLevel.DEBUG) > 0) { 39 | Log.d(tag, msg); 40 | } 41 | } 42 | 43 | public static void d(String tag, String msg, Throwable tr) { 44 | if ((logFlags & LogLevel.DEBUG) > 0) { 45 | Log.d(tag, msg, tr); 46 | } 47 | } 48 | 49 | public static void i(String tag, String msg) { 50 | if ((logFlags & LogLevel.INFO) > 0) { 51 | Log.i(tag, msg); 52 | } 53 | } 54 | 55 | public static void i(String tag, String msg, Throwable tr) { 56 | if ((logFlags & LogLevel.INFO) > 0) { 57 | Log.i(tag, msg, tr); 58 | } 59 | } 60 | 61 | public static void w(String tag, String msg) { 62 | if ((logFlags & LogLevel.WARNINGS) > 0) { 63 | Log.w(tag, msg); 64 | } 65 | } 66 | 67 | public static void w(String tag, String msg, Throwable tr) { 68 | if ((logFlags & LogLevel.WARNINGS) > 0) { 69 | Log.w(tag, msg, tr); 70 | } 71 | } 72 | 73 | public static void e(String tag, String msg) { 74 | if ((logFlags & LogLevel.ERRORS) > 0) { 75 | Log.e(tag, msg); 76 | } 77 | } 78 | 79 | public static void e(String tag, String msg, Throwable tr) { 80 | if ((logFlags & LogLevel.ERRORS) > 0) { 81 | Log.e(tag, msg, tr); 82 | } 83 | } 84 | 85 | @TargetApi(Build.VERSION_CODES.FROYO) 86 | public static void wtf(String tag, String msg) { 87 | if ((logFlags & LogLevel.WTF) > 0) { 88 | Log.wtf(tag, msg); 89 | } 90 | } 91 | 92 | @TargetApi(Build.VERSION_CODES.FROYO) 93 | public static void wtf(String tag, String msg, Throwable tr) { 94 | if ((logFlags & LogLevel.WTF) > 0) { 95 | Log.wtf(tag, msg, tr); 96 | } 97 | } 98 | 99 | 100 | public static class LogLevel { 101 | public static final int VERBOSE = Integer.parseInt("000001", 2); 102 | public static final int DEBUG = Integer.parseInt("000010", 2); 103 | public static final int INFO = Integer.parseInt("000100"); 104 | public static final int WARNINGS = Integer.parseInt("001000", 2); 105 | public static final int ERRORS = Integer.parseInt("010000", 2); 106 | public static final int WTF = Integer.parseInt("100000", 2); 107 | public static final int ALL = VERBOSE | DEBUG | INFO | WARNINGS | ERRORS | WTF; 108 | public static final int NONE = 0; 109 | } 110 | } -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/collections/TransactionalHashSet.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.collections; 2 | 3 | import java.util.Collection; 4 | import java.util.HashSet; 5 | 6 | /** 7 | * Extension of a {@link HashSet} which allows performing transactions which 8 | * are not committed until the transaction is ended. This can be used around 9 | * an iteration so that the iteration may modify the collection and commit 10 | * the changes after the iteration completes. 11 | *

12 | * Note that this implementation makes liberal use of synchronization and extra 13 | * operations, so the performance will likely be worse than a standard 14 | * {@link HashSet}. 15 | * 16 | * @param The type of items in the {@link HashSet} 17 | */ 18 | public class TransactionalHashSet extends HashSet { 19 | 20 | private static final long serialVersionUID = -7093089056781106409L; 21 | 22 | 23 | private HashSet toAdd, toRemove; 24 | 25 | private volatile boolean inTransaction; 26 | 27 | 28 | public TransactionalHashSet() { 29 | super(); 30 | init(); 31 | } 32 | 33 | public TransactionalHashSet(Collection collection) { 34 | super(collection); 35 | init(); 36 | } 37 | 38 | private void init() { 39 | inTransaction = false; 40 | toAdd = new HashSet(); 41 | toRemove = new HashSet(); 42 | } 43 | 44 | 45 | /** 46 | * Starts a transaction. This causes additions and removals to be stored, 47 | * but they will not be performed until a call to {@link #endTransaction()} 48 | */ 49 | public void beginTransaction() { 50 | synchronized (this) { 51 | inTransaction = true; 52 | } 53 | } 54 | 55 | /** 56 | * Commits all pending operations and returns the {@link HashSet} to a 57 | * normal state where operations are committed immediately. 58 | */ 59 | public void endTransaction() { 60 | synchronized (this) { 61 | // Commit the transaction if we're in one 62 | if (inTransaction) { 63 | // Lower the flag so we actually commit operations 64 | inTransaction = false; 65 | 66 | // Add all the items we need to add 67 | for (T item : toAdd) { 68 | add(item); 69 | } 70 | 71 | // Remove all the items we need to remove 72 | for (T item : toRemove) { 73 | remove(item); 74 | } 75 | 76 | toAdd.clear(); 77 | toRemove.clear(); 78 | } 79 | } 80 | } 81 | 82 | @Override 83 | public boolean add(T object) { 84 | synchronized (this) { 85 | if (inTransaction) { 86 | // If we're in a transaction, add it to the toAdd list 87 | toAdd.add(object); 88 | // The item already exists if it's in this set or the add set 89 | boolean exists = (contains(object) || toAdd.contains(object)); 90 | // Since this is happening later in the transaction, undo any 91 | // removal 92 | boolean wasToBeRemoved = toRemove.remove(object); 93 | 94 | // If it was to be removed, we just added it back 95 | if (wasToBeRemoved) return true; 96 | // Otherwise it was added if it didn't already exist 97 | else return !exists; 98 | } else { 99 | // Not in a transaction, proceed as normal 100 | return super.add(object); 101 | } 102 | } 103 | } 104 | 105 | @Override 106 | public boolean addAll(Collection collection) { 107 | // Take the lock here so we're not acquiring/releasing it repeatedly 108 | synchronized (this) { 109 | return super.addAll(collection); 110 | } 111 | } 112 | 113 | 114 | @SuppressWarnings("unchecked") 115 | @Override 116 | public boolean remove(Object object) { 117 | synchronized (this) { 118 | if (inTransaction) { 119 | // If the remove list already contains it, we aren't doing 120 | // anything 121 | if (toRemove.contains(object)) { 122 | return false; 123 | } else { 124 | // If it's in either set, we're doing a removal 125 | if (toAdd.remove(object) || contains(object)) { 126 | toRemove.add((T) object); 127 | return true; 128 | } else { 129 | return false; 130 | } 131 | } 132 | } else { 133 | // Not in a transaction, proceed as normal 134 | return super.remove(object); 135 | } 136 | } 137 | } 138 | 139 | @Override 140 | public boolean removeAll(Collection collection) { 141 | // Take the lock here so we're not acquiring/releasing it repeatedly 142 | synchronized (this) { 143 | return super.removeAll(collection); 144 | } 145 | } 146 | 147 | @Override 148 | public void clear() { 149 | removeAll(this); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/collections/CategorizedList.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.collections; 2 | 3 | import com.raizlabs.coreutils.functions.Predicate; 4 | import com.raizlabs.coreutils.threading.ThreadingUtils; 5 | import com.raizlabs.coreutils.util.observable.lists.ListObserver; 6 | import com.raizlabs.coreutils.util.observable.lists.ObservableList; 7 | import com.raizlabs.coreutils.util.observable.lists.ObservableListWrapper; 8 | import com.raizlabs.coreutils.util.observable.lists.SimpleListObserverListener; 9 | 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | 13 | /** 14 | * Container that places a list of items into different categories 15 | * @param The type of item contained in the list 16 | */ 17 | public class CategorizedList { 18 | 19 | /** 20 | * Categorize data by having the data item specify what specific field or fields to use 21 | * 22 | * @param type of data item from which to retrieve the categories 23 | */ 24 | public interface Categorizer { 25 | 26 | /** 27 | * 28 | * @param data The item to retrieve a category from. 29 | * @return The array of categories for this item 30 | */ 31 | public String[] getCategories(Data data); 32 | } 33 | 34 | private ProxyObservableList proxyDataList; 35 | private Categorizer categorizer; 36 | 37 | private final HashMap> filteredLists = new HashMap<>(); 38 | private ObservableList categories = new ObservableListWrapper<>(); 39 | 40 | public ObservableList getAllCategories() { 41 | return categories; 42 | } 43 | 44 | public ObservableList getAllData() { 45 | return proxyDataList; 46 | } 47 | 48 | /** 49 | * Populates this {@link CategorizedList} with the provided source list and a {@link Categorizer} 50 | * that dictates how the items are to be categorized 51 | * @param sourceList list containing the original data 52 | * @param categorizer {@link Categorizer} that dicates how the items are to be categorized 53 | */ 54 | public CategorizedList(ObservableList sourceList, Categorizer categorizer) { 55 | this.proxyDataList = new ProxyObservableList<>(sourceList); 56 | this.categorizer = categorizer; 57 | 58 | this.proxyDataList.getListObserver().addListener(new SimpleListObserverListener() { 59 | @Override 60 | public void onGenericChange(ListObserver observer) { 61 | updateCategories(); 62 | } 63 | }); 64 | updateCategories(); 65 | } 66 | 67 | /** 68 | * Loads items from the provided {@link ObservableList} into this container 69 | * @param sourceList list containing the data items to insert 70 | */ 71 | public void loadData(ObservableList sourceList) { 72 | if (sourceList == null) { 73 | sourceList = new ObservableListWrapper<>(); 74 | } 75 | 76 | final ObservableList finalSource = sourceList; 77 | 78 | ThreadingUtils.runOnUIThread(new Runnable() { 79 | @Override 80 | public void run() { 81 | proxyDataList.setSourceList(finalSource); 82 | } 83 | }); 84 | } 85 | 86 | /** 87 | * Gets a {@link ObservableList} that contains the data associated with the given category 88 | * @param category Name of the category 89 | * @return {@link ObservableList} of items that are associated with the given category 90 | */ 91 | public ObservableList getDataForCategory(final String category) { 92 | synchronized (filteredLists) { 93 | FilteredList categoryList = filteredLists.get(category); 94 | 95 | if (categoryList == null) { 96 | categoryList = new FilteredList<>(proxyDataList, new Predicate() { 97 | @Override 98 | public boolean evaluate(Data item) { 99 | String[] categoriesArray = categorizer.getCategories(item); 100 | 101 | if (categoriesArray == null) { 102 | return true; 103 | } else { 104 | for (String categoryItem : categoriesArray) { 105 | if (category.equals(categoryItem)) { 106 | return true; 107 | } 108 | } 109 | return false; 110 | } 111 | } 112 | }); 113 | 114 | filteredLists.put(category, categoryList); 115 | } 116 | return categoryList; 117 | } 118 | } 119 | 120 | private void updateCategories() { 121 | 122 | HashSet addedTypes = new HashSet<>(); 123 | categories.beginTransaction(); 124 | categories.clear(); 125 | for(Data data : proxyDataList) { 126 | String[] categoriesArray = categorizer.getCategories(data); 127 | for(String category : categoriesArray) { 128 | addedTypes.add(category); 129 | } 130 | } 131 | categories.addAll(addedTypes); 132 | categories.endTransaction(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/threading/ThreadingUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.threading; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.view.View; 6 | 7 | public class ThreadingUtils { 8 | 9 | private static Handler uiHandler; 10 | 11 | /** 12 | * @return A {@link Handler} that is bound to the UI thread. 13 | */ 14 | public static Handler getUIHandler() { 15 | if (uiHandler == null) uiHandler = new Handler(Looper.getMainLooper()); 16 | return uiHandler; 17 | } 18 | 19 | /** 20 | * Returns true if this function was called on the thread the given 21 | * {@link Handler} is bound to. 22 | * 23 | * @param handler The {@link Handler} to check the thread of. 24 | * @return True if this function was called on the {@link Handler}'s 25 | * thread. 26 | */ 27 | public static boolean isOnHandlerThread(Handler handler) { 28 | Looper handlerLooper = handler.getLooper(); 29 | if (handlerLooper != null) { 30 | return handlerLooper.equals(Looper.myLooper()); 31 | } 32 | 33 | return false; 34 | } 35 | 36 | /** 37 | * @return True if this function was called from the UI thread 38 | */ 39 | public static boolean isOnUIThread() { 40 | return Looper.getMainLooper().equals(Looper.myLooper()); 41 | } 42 | 43 | 44 | /** 45 | * Runs the given {@link Runnable} on the thread the given {@link Handler} 46 | * is bound to. This will execute immediately, before this function returns, 47 | * if this function was already called on the given {@link Handler}'s thread. 48 | * Otherwise, the {@link Runnable} will be posted to the {@link Handler}. 49 | * 50 | * @param action The {@link Runnable} to execute. 51 | * @param handler The {@link Handler} to run the action on. 52 | * @return True if the action was already executed before this funcion 53 | * returned, or false if the action was posted to be handled later. 54 | */ 55 | public static boolean runOnHandler(Runnable action, Handler handler) { 56 | if (isOnHandlerThread(handler)) { 57 | action.run(); 58 | return true; 59 | } else { 60 | handler.post(action); 61 | return false; 62 | } 63 | } 64 | 65 | /** 66 | * Runs the given {@link Runnable} on the UI thread. This will execute 67 | * immediately, before this function returns, if this function was called 68 | * on the UI thread. Otherwise, the {@link Runnable} will be posted to the 69 | * UI thread. 70 | * 71 | * @param action The {@link Runnable} to execute on the UI thread. 72 | * @return True if the action was already executed before this function 73 | * returned, or false if the action was posted to be handled later. 74 | * @see #runOnUIThread(Runnable, Handler) 75 | * @see #runOnUIThread(Runnable, View) 76 | */ 77 | public static boolean runOnUIThread(Runnable action) { 78 | if (isOnUIThread()) { 79 | action.run(); 80 | return true; 81 | } else { 82 | getUIHandler().post(action); 83 | return false; 84 | } 85 | } 86 | 87 | /** 88 | * Runs the given {@link Runnable} on the UI thread. This will execute 89 | * immediately, before this function returns, if this function was called 90 | * on the UI thread. Otherwise, the {@link Runnable} will be posted using 91 | * the given {@link View}. 92 | *

93 | * NOTE: This method will attempt to force the action onto the UI thread. 94 | *

95 | * WARNING: The action may still not be taken if the view's 96 | * {@link View#post(Runnable)} method returns true, but doesn't execute. 97 | * (This is the case when the view is not attached to a window). 98 | * 99 | * @param action The {@link Runnable} to execute. 100 | * @param v A {@link View} to use to post the {@link Runnable} if this 101 | * wasn't called on the UI thread. 102 | * @return True if the action was already executed before this function 103 | * returned, or false if the action was posted. 104 | * @see #runOnUIThread(Runnable) 105 | * @see #runOnUIThread(Runnable, Handler) 106 | */ 107 | public static boolean runOnUIThread(Runnable action, View v) { 108 | if (isOnUIThread()) { 109 | action.run(); 110 | return true; 111 | } else { 112 | if (!v.post(action)) { 113 | runOnUIThread(action); 114 | } 115 | return false; 116 | } 117 | } 118 | 119 | /** 120 | * Runs the given {@link Runnable} immediately if this function is called 121 | * on the UI thread. Otherwise, it is posted to the given {@link Handler} 122 | * and executed on its bound thread. Though it is assumed that the given 123 | * {@link Handler} is bound to the UI thread, it is not necessary, and it 124 | * will execute the action either way. 125 | * 126 | * @param action The {@link Runnable} to execute. 127 | * @param handler The {@link Handler} to post the action to if if this 128 | * wasn't called on the UI thread. 129 | * @return True if the action was already executed before this function 130 | * returned, or false if the action was posted to the {@link Handler}. 131 | */ 132 | public static boolean runOnUIThread(Runnable action, Handler handler) { 133 | if (isOnUIThread()) { 134 | action.run(); 135 | return true; 136 | } else { 137 | if (!handler.post(action)) { 138 | runOnUIThread(action); 139 | } 140 | return false; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/io/IOUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.io; 2 | 3 | import android.util.Log; 4 | 5 | import com.raizlabs.coreutils.logging.Logger; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.ByteArrayInputStream; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.Closeable; 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.InputStreamReader; 15 | import java.io.OutputStream; 16 | 17 | public class IOUtils { 18 | 19 | /** 20 | * Utility method for pulling plain text from an InputStream object 21 | * 22 | * @param in InputStream object retrieved from an HttpResponse 23 | * @return String contents of stream 24 | */ 25 | public static String readStream(InputStream in) { 26 | BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 27 | StringBuilder sb = new StringBuilder(); 28 | String line = null; 29 | try { 30 | while ((line = reader.readLine()) != null) { 31 | sb.append(line + "\n"); 32 | } 33 | } catch (IOException e) { 34 | Logger.e(IOUtils.class.getSimpleName(), "Error reading stream", e); 35 | } finally { 36 | safeClose(in); 37 | safeClose(reader); 38 | } 39 | return sb.toString(); 40 | } 41 | 42 | /** 43 | * Reads the given input stream into a byte array. Note that this is done 44 | * entirely in memory, so the input should not be very large. 45 | * 46 | * @param input The stream to read. 47 | * @return The byte array containing all the data from the input stream or 48 | * null if there was a problem. 49 | */ 50 | public static byte[] readStreamBytes(InputStream input) { 51 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 52 | if (copyStream(input, outputStream)) { 53 | return outputStream.toByteArray(); 54 | } 55 | 56 | return null; 57 | } 58 | 59 | /** 60 | * Feeds the entire input stream into the output stream. 61 | * 62 | * @param input The stream to copy from. 63 | * @param output The stream to copy into. 64 | * @return True if the copy succeeded, false if it failed. 65 | */ 66 | public static boolean copyStream(InputStream input, OutputStream output) { 67 | return copyStream(input, output, 4096); 68 | } 69 | 70 | /** 71 | * Feeds the entire input stream into the output stream. 72 | * 73 | * @param input The stream to copy from. 74 | * @param output The stream to copy into. 75 | * @param bufferSize The size of the buffer to use. 76 | * @return True if the copy succeeded, false if it failed. 77 | */ 78 | public static boolean copyStream(InputStream input, OutputStream output, int bufferSize) { 79 | byte[] buffer = new byte[bufferSize]; 80 | 81 | try { 82 | int bytesRead; 83 | while ((bytesRead = input.read(buffer)) != -1) { 84 | output.write(buffer, 0, bytesRead); 85 | } 86 | 87 | return true; 88 | } catch (IOException e) { 89 | Log.w(IOUtils.class.getSimpleName(), "Error copying stream", e); 90 | return false; 91 | } 92 | } 93 | 94 | /** 95 | * Safely closes the given {@link Closeable} without throwing 96 | * any exceptions due to null pointers or {@link IOException}s. 97 | * 98 | * @param closeable The {@link Closeable} to close. 99 | */ 100 | public static void safeClose(Closeable closeable) { 101 | if (closeable != null) { 102 | try { 103 | closeable.close(); 104 | } catch (IOException e) { 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * Creates an {@link InputStream} from the data in the given string. 111 | * 112 | * @param str The string to set as the contents of the stream. 113 | * @return The created {@link InputStream} 114 | */ 115 | public static InputStream getInputStream(String str) { 116 | return new ByteArrayInputStream(str.getBytes()); 117 | } 118 | 119 | 120 | /** 121 | * Deletes the file or directory at the given path using the rm shell 122 | * command. 123 | * 124 | * @param path Path to the file to delete. 125 | * @param recursive True to do the delete recursively, else false. 126 | * @return True if the file existed and was deleted, or false if it didn't 127 | * exist. 128 | * @throws IOException if the shell execution fails. 129 | */ 130 | public static boolean deleteViaShell(String path, boolean recursive) 131 | throws IOException { 132 | File file = new File(path); 133 | if (file.exists()) { 134 | String deleleteCommand = (recursive ? "rm -r " : "rm ") + path; 135 | Runtime runtime = Runtime.getRuntime(); 136 | runtime.exec(deleleteCommand); 137 | return true; 138 | } 139 | return false; 140 | } 141 | 142 | 143 | /** 144 | * Deletes the file or directory at the given path using the rm shell 145 | * command. 146 | * 147 | * @param file The {@link File} to delete. 148 | * @param recursive True to do the delete recursively, else false. 149 | * @return True if the file existed and was deleted, or false if it didn't 150 | * exist. 151 | * @throws IOException if the shell execution fails. 152 | */ 153 | public static boolean deleteViaShell(File file, boolean recursive) 154 | throws IOException { 155 | if (file.exists()) { 156 | String deleleteCommand = (recursive ? "rm -r " : "rm ") 157 | + file.getAbsolutePath(); 158 | Runtime runtime = Runtime.getRuntime(); 159 | runtime.exec(deleleteCommand); 160 | return true; 161 | } 162 | return false; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/json/JSONHelper.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.json; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Iterator; 12 | import java.util.List; 13 | 14 | /** 15 | * Class with common utilities for JSON parsing 16 | */ 17 | public class JSONHelper { 18 | 19 | /** 20 | * Delegate which is executed on individual keys of a {@link JSONObject}. 21 | */ 22 | public interface JSONKeyDelegate { 23 | /** 24 | * Called to execute this delegate on the given key. 25 | * 26 | * @param json The JSONObject the key belongs to. 27 | * @param key The key to handle. 28 | */ 29 | public void execute(JSONObject json, String key); 30 | } 31 | 32 | /** 33 | * Runs the given delegate on all of the keys in the given JSON object. 34 | * 35 | * @param json The JSON whose keys to run over. 36 | * @param delegate The delegate to run on each key. 37 | */ 38 | public static void runOverKeys(JSONObject json, JSONKeyDelegate delegate) { 39 | if (json != null) { 40 | // JSON uses a set of Strings and returns an Iterator 41 | // but doesn't clarify in the method definition. 42 | // Scary, but always works under the current implementation. 43 | @SuppressWarnings("unchecked") 44 | Iterator iterator = json.keys(); 45 | while (iterator.hasNext()) { 46 | delegate.execute(json, iterator.next()); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Iterates over the {@link JSONArray} defined at the given key in the 53 | * given JSON object, if one exists, and parses each element with the given 54 | * {@link JSONArrayParserDelegate}. If the delegate returns an item, it 55 | * will be added to the result list. If it does not, that index will be 56 | * skipped - no null will be added to the list. 57 | * 58 | * @param json The {@link JSONObject} to get the array from 59 | * @param key The key to look for the array under 60 | * @param delegate The {@link JSONArrayParserDelegate} to call to parse 61 | * each object 62 | * @return A {@link List} containing all parsed objects or null if the key 63 | * didn't map to a {@link JSONArray} 64 | */ 65 | public static List parseJSONArray(JSONObject json, String key, 66 | JSONArrayParserDelegate delegate) { 67 | JSONArray array = json.optJSONArray(key); 68 | if (array == null) return null; 69 | 70 | return parseJSONArray(array, delegate); 71 | } 72 | 73 | /** 74 | * Iterates over the given {@link JSONArray} and parses each element with 75 | * the given {@link JSONArrayParserDelegate}. If the delegate returns an 76 | * item, it will be added to the result list. If it does not, that index 77 | * will be skipped - no null will be added to the list. 78 | * 79 | * @param array The {@link JSONArray} to parse 80 | * @param delegate The {@link JSONArrayParserDelegate} to call to parse 81 | * each object 82 | * @return A {@link List} containing all parsed objects 83 | */ 84 | public static List parseJSONArray(JSONArray array, 85 | JSONArrayParserDelegate delegate) { 86 | 87 | final int numItems = (array == null) ? 0 : array.length(); 88 | 89 | if (numItems == 0) { 90 | return new ArrayList(0); 91 | } else { 92 | List items = new ArrayList(numItems); 93 | 94 | for (int i = 0; i < numItems; i++) { 95 | try { 96 | JSONObject objectJSON = array.getJSONObject(i); 97 | 98 | T obj = delegate.parseObject(objectJSON); 99 | if (obj != null) items.add(obj); 100 | } catch (JSONException e) { 101 | // This shouldn't really ever happen since we are checking 102 | // the length 103 | Log.d(JSONHelper.class.getName(), "Error parsing object", e); 104 | } 105 | } 106 | 107 | return items; 108 | } 109 | } 110 | 111 | /** 112 | * Parses all strings out of the data at the given key inside the given 113 | * {@link JSONObject} if it exists 114 | * 115 | * @param json The {@link JSONObject} to look for the key in 116 | * @param key The key to look for the strings under 117 | * @return The list of strings, or null if they couldn't be parsed or the 118 | * key wasn't defined 119 | */ 120 | public static List parseStrings(JSONObject json, String key) { 121 | // Attempt to grab the messages as an array of messages 122 | List strings = parseStrings(json.optJSONArray(key)); 123 | if (strings == null) { 124 | // If there is only one string, the field will be a simple string 125 | // mapping instead of an array of size 1 :( 126 | String string = json.optString(key); 127 | if (!TextUtils.isEmpty(string)) { 128 | strings = new ArrayList(1); 129 | strings.add(string); 130 | } 131 | } 132 | 133 | return strings; 134 | } 135 | 136 | /** 137 | * Parses all strings out of the given {@link JSONArray} into a list. 138 | * 139 | * @param json The array to parse. 140 | * @return A list containing all the strings or null if the input array 141 | * was null. 142 | */ 143 | public static List parseStrings(JSONArray json) { 144 | if (json != null) { 145 | 146 | final int numStrings = json.length(); 147 | 148 | List strings = new ArrayList(numStrings); 149 | 150 | for (int i = 0; i < numStrings; i++) { 151 | String string = json.optString(i); 152 | if (!TextUtils.isEmpty(string)) strings.add(string); 153 | } 154 | 155 | return strings; 156 | } 157 | return null; 158 | } 159 | } -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/view/ViewUtils.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.text.TextUtils; 6 | import android.util.DisplayMetrics; 7 | import android.util.TypedValue; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | 11 | public class ViewUtils { 12 | 13 | /** 14 | * Returns the text for a given {@link textView} or else null if 15 | * the textView is null or has no text within. 16 | * 17 | * @param textView The textView whose text should be returned 18 | * @return the text within the textView or null 19 | */ 20 | public static CharSequence getTextOrNull(TextView textView) { 21 | if (textView == null) return null; 22 | 23 | CharSequence text = textView.getText(); 24 | return !TextUtils.isEmpty(text) ? text : null; 25 | } 26 | 27 | /** 28 | * Returns the a String with the text for a given {@link textView} 29 | * or else null if the textView is null or has no text within. 30 | *

31 | * Overload for {@link #getTextOrNull(TextView)} which returns a {@link String} 32 | *

33 | * 34 | * @param textView The textView whose text should be returned 35 | * @return the a String of the text within the textView or null 36 | */ 37 | public static String getStringTextOrNull(TextView textView) { 38 | CharSequence text = getTextOrNull(textView); 39 | return (text != null) ? text.toString() : null; 40 | } 41 | 42 | /** 43 | * Converts the given value in density-independent pixels into raw pixels 44 | * with respect to the metrics of the given {@link View} 45 | * 46 | * @param dips The value in density-independent pixels 47 | * @param view The {@link View} whose metrics to use to do the conversion 48 | * @return The value in raw pixels 49 | */ 50 | public static float dipsToPixels(float dips, View view) { 51 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 52 | dips, view.getResources().getDisplayMetrics()); 53 | } 54 | 55 | /** 56 | * Converts the given value in density-independent pixels into raw pixels 57 | * with respect to the metrics of the given {@link Resources} 58 | * 59 | * @param dips The value in density-independent pixels 60 | * @param resources The {@link Resources} whose metrics to use to do the 61 | * conversion 62 | * @return The value in raw pixels 63 | */ 64 | public static float dipsToPixels(float dips, Resources resources) { 65 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 66 | dips, resources.getDisplayMetrics()); 67 | } 68 | 69 | /** 70 | * Converts the given value in density-independent pixels into raw pixels 71 | * with respect to the given {@link DisplayMetrics} 72 | * 73 | * @param dips The value in density-independent pixels 74 | * @param metrics The {@link DisplayMetrics} to use to do the conversion 75 | * @return The value in raw pixels 76 | */ 77 | public static float dipsToPixels(float dips, DisplayMetrics metrics) { 78 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 79 | dips, metrics); 80 | } 81 | 82 | /** 83 | * Sets the given {@link View}s visibility to {@link View#VISIBLE} or 84 | * {@link View#GONE} depending on the specified value. 85 | * 86 | * @param view The {@link View} to change the visibility of. 87 | * @param visible True to make the view VISIBLE, false to make it GONE. 88 | */ 89 | public static void setVisibleOrGone(View view, boolean visible) { 90 | view.setVisibility(visible ? View.VISIBLE : View.GONE); 91 | } 92 | 93 | /** 94 | * Sets the given {@link View} visibility to {@link View#VISIBLE} or 95 | * {@link View#GONE} depending on the specified value. 96 | * 97 | * @param visible True to make the view VISIBLE, false to make it GONE. 98 | * @param views The list of views to change the visiblity of 99 | */ 100 | public static void setViewsVisibleOrGone(boolean visible, View... views) { 101 | for (View view : views) { 102 | setVisibleOrGone(view, visible); 103 | } 104 | } 105 | 106 | /** 107 | * Sets the given {@link View}s visibility to {@link View#VISIBLE} or 108 | * {@link View#INVISIBLE} depending on the specified value. 109 | * 110 | * @param view The {@link View} to change the visibility of. 111 | * @param visible True to make the view VISIBLE, false to make it INVISIBLE. 112 | */ 113 | public static void setVisibleOrInvisible(View view, boolean visible) { 114 | view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 115 | } 116 | 117 | /** 118 | * Sets the given {@link View} visibility to {@link View#VISIBLE} or 119 | * {@link View#INVISIBLE} depending on the specified value. 120 | * 121 | * @param visible True to make the view VISIBLE, false to make it INVISIBLE. 122 | * @param views The list of views to change the visiblity of 123 | */ 124 | public static void setViewsVisibleOrInvisible(boolean visible, View... views) { 125 | for (View view : views) { 126 | setVisibleOrGone(view, visible); 127 | } 128 | } 129 | 130 | /** 131 | * Returns true if the given {@link View}s visibility is set to 132 | * {@link View#VISIBLE}. 133 | * 134 | * @param view The {@link View} to check. 135 | * @return True if the view is set to VISIBLE. 136 | */ 137 | public static boolean isVisible(View view) { 138 | return view.getVisibility() == View.VISIBLE; 139 | } 140 | 141 | /** 142 | * Returns true if the given {@link View}s visibility is set to 143 | * {@link View#INVISIBLE}. 144 | * 145 | * @param view The {@link View} to check. 146 | * @return True if the view is set to INVISIBLE. 147 | */ 148 | public static boolean isInvisible(View view) { 149 | return view.getVisibility() == View.INVISIBLE; 150 | } 151 | 152 | /** 153 | * Returns true if the given {@link View}s visibility is set to 154 | * {@link View#GONE}. 155 | * 156 | * @param view The {@link View} to check. 157 | * @return True if the view is set to GONE. 158 | */ 159 | public static boolean isGone(View view) { 160 | return view.getVisibility() == View.GONE; 161 | } 162 | 163 | /** 164 | * Returns the height of the actionBar. 165 | * 166 | * @param context 167 | * @return height, in pixels, of the {@link android.app.ActionBar} 168 | */ 169 | public static int getActionBarHeight(Context context) { 170 | int actionBarHeight = 0; 171 | TypedValue tv = new TypedValue(); 172 | if ((context != null) && (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))) { 173 | actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics()); 174 | } 175 | return actionBarHeight; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/collections/CategorizedListFlattener.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.collections; 2 | 3 | import com.raizlabs.coreutils.events.WeakDelegateListObserverListener; 4 | import com.raizlabs.coreutils.util.observable.lists.ListObserver; 5 | import com.raizlabs.coreutils.util.observable.lists.ObservableListWrapper; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.Comparator; 10 | import java.util.List; 11 | 12 | 13 | /** 14 | * Class which takes a {@link CategorizedList} and "flattens" it into one list of categories and 15 | * items such that it can be inserted into an adapter 16 | * 17 | * @param The type of data in the categorized list. 18 | */ 19 | public class CategorizedListFlattener extends ObservableListWrapper> { 20 | 21 | public interface FlattenedItem { 22 | /** 23 | * @return True if this item represents a category. 24 | */ 25 | public boolean isCategory(); 26 | 27 | /** 28 | * @return The name of this category if it is one, or null. 29 | */ 30 | public String getCategoryName(); 31 | 32 | /** 33 | * @return The data of this item if it is a data item, or null. 34 | */ 35 | public Data getData(); 36 | } 37 | 38 | private CategorizedList categorizedList; 39 | private Comparator categoryComparator; 40 | private Comparator dataComparator; 41 | 42 | /** 43 | * Constructs a new flattener which will reflect the contents of the given 44 | * categorized list, updating itself accordingly. 45 | * @param categorizedList The categorized list to flatten the elements of. 46 | */ 47 | public CategorizedListFlattener(CategorizedList categorizedList) { 48 | this(categorizedList, null, null); 49 | } 50 | 51 | /** 52 | * Constructs a new flattener which will reflect the contents of the given 53 | * categorized list, updating itself accordingly. 54 | * @param categorizedList The categorized list to flatten the elements of. 55 | * @param categoryComparator A comparator to use to sort the categories. 56 | * @param dataComparator A comparator to use to sort the data elements. 57 | */ 58 | public CategorizedListFlattener(CategorizedList categorizedList, 59 | Comparator categoryComparator, 60 | Comparator dataComparator) { 61 | super(); 62 | this.categorizedList = categorizedList; 63 | this.categoryComparator = categoryComparator; 64 | this.dataComparator = dataComparator; 65 | 66 | // Listen for any updates from the categories or the items themselves as 67 | // either will invalidate our data. 68 | // Use weak listeners to ourself so we don't get "leaked" / retained by 69 | // the other list 70 | categorizedList.getAllCategories().getListObserver().addListener(new ChangeListener(this)); 71 | categorizedList.getAllData().getListObserver().addListener(new ChangeListener(this)); 72 | 73 | // Do an initial update to populate our contents 74 | update(); 75 | } 76 | 77 | /** 78 | * Rebuilds our list so that we contain all of the flattened items. 79 | */ 80 | private void update() { 81 | synchronized (this) { 82 | beginTransaction(); 83 | clear(); 84 | List categories = new ArrayList<>(categorizedList.getAllCategories()); 85 | if (categoryComparator != null) { 86 | Collections.sort(categories, categoryComparator); 87 | } 88 | 89 | for (String category : categories) { 90 | add(new CategoryItem(category)); 91 | 92 | List items = new ArrayList<>(categorizedList.getDataForCategory(category)); 93 | if (dataComparator != null) { 94 | Collections.sort(items, dataComparator); 95 | } 96 | 97 | for (Data data : categorizedList.getDataForCategory(category)) { 98 | add(new DataItem<>(data)); 99 | } 100 | } 101 | endTransaction(); 102 | } 103 | } 104 | 105 | /** 106 | * General listener class which causes updates when the data changes. Uses 107 | * weak references to avoid leaks of the flattener. 108 | * @param The type of data being observed for changes. 109 | * @param The data type of the flattener. 110 | */ 111 | private static class ChangeListener extends WeakDelegateListObserverListener> { 112 | 113 | /** 114 | * Constructs a listener which will cause updates on the given flattener 115 | * when it is notified of changes. 116 | */ 117 | public ChangeListener(CategorizedListFlattener flattener) { 118 | super(flattener); 119 | } 120 | 121 | private void onChange(ListObserver observer) { 122 | CategorizedListFlattener flattener = getDelegate(observer); 123 | if (flattener != null) { 124 | flattener.update(); 125 | } 126 | } 127 | 128 | @Override 129 | public void onItemRangeChanged(ListObserver observer, int startPosition, int itemCount) { 130 | onChange(observer); 131 | } 132 | 133 | @Override 134 | public void onItemRangeInserted(ListObserver observer, int startPosition, int itemCount) { 135 | onChange(observer); 136 | } 137 | 138 | @Override 139 | public void onItemRangeRemoved(ListObserver observer, int startPosition, int itemCount) { 140 | onChange(observer); 141 | } 142 | 143 | @Override 144 | public void onGenericChange(ListObserver observer) { 145 | onChange(observer); 146 | } 147 | } 148 | 149 | private static class CategoryItem implements FlattenedItem { 150 | 151 | private String name; 152 | 153 | public CategoryItem(String categoryName) { 154 | name = categoryName; 155 | } 156 | 157 | @Override 158 | public boolean isCategory() { 159 | return true; 160 | } 161 | 162 | @Override 163 | public String getCategoryName() { 164 | return name; 165 | } 166 | 167 | @Override 168 | public Data getData() { 169 | return null; 170 | } 171 | } 172 | 173 | private static class DataItem implements FlattenedItem { 174 | 175 | private Data item; 176 | 177 | public DataItem(Data item) { 178 | this.item = item; 179 | } 180 | 181 | @Override 182 | public boolean isCategory() { 183 | return false; 184 | } 185 | 186 | @Override 187 | public String getCategoryName() { 188 | return null; 189 | } 190 | 191 | @Override 192 | public Data getData() { 193 | return item; 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/widget/ImageMixView.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Rect; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | import com.raizlabs.coreutils.math.MathUtils; 12 | 13 | /** 14 | * View which mixes between two images by showing some portion of one image and 15 | * the rest of another. 16 | *

17 | * Note: The view is sized based off the first image. 18 | */ 19 | public class ImageMixView extends View { 20 | /** 21 | * Possible directions for the mix. 22 | * 23 | * @author Dylan James 24 | */ 25 | public static class MixDirection { 26 | public static final int VERTICAL = 0; // 0b0 27 | public static final int HORIZONTAL = 2; // 0b10 28 | static final int REVERSE = 1; // 0b01 29 | 30 | public static final int VERTICAL_REVERSE = VERTICAL | REVERSE; 31 | public static final int HORIZONTAL_REVERSE = HORIZONTAL | REVERSE; 32 | } 33 | 34 | private int mixDirection; 35 | 36 | /** 37 | * Sets the mix direction for this view. 38 | * 39 | * @param direction One of {@link MixDirection} which represents the 40 | * direction to do the mix. 41 | */ 42 | public void setMixDirection(int direction) { 43 | mixDirection = direction; 44 | } 45 | 46 | // Rectangles to sample from in the source images 47 | Rect src1, src2; 48 | // Rectangles to draw to in the output canvas 49 | Rect dst1, dst2; 50 | 51 | private Bitmap bitmap1, bitmap2; 52 | 53 | /** 54 | * Sets the first image of the mix. 55 | * 56 | * @param bmp The {@link Bitmap} to use as the first image. 57 | */ 58 | public void setFirstImage(Bitmap bmp) { 59 | bitmap1 = bmp; 60 | src1 = new Rect(0, 0, bitmap1.getWidth(), bitmap1.getHeight()); 61 | requestLayout(); 62 | postInvalidate(); 63 | } 64 | 65 | /** 66 | * Sets the first image of the mix. 67 | * 68 | * @param id The resource id of the image to use as the first image. 69 | */ 70 | public void setFirstImageResource(int id) { 71 | setFirstImage(BitmapFactory.decodeResource(getResources(), id)); 72 | } 73 | 74 | /** 75 | * Sets the second image of the mix. 76 | * 77 | * @param bmp The {@link Bitmap} to use as the second image. 78 | */ 79 | public void setSecondImage(Bitmap bmp) { 80 | bitmap2 = bmp; 81 | src2 = new Rect(0, 0, bitmap2.getWidth(), bitmap2.getHeight()); 82 | postInvalidate(); 83 | } 84 | 85 | /** 86 | * Sets the second image of the mix. 87 | * 88 | * @param id The resource id of the image to use as the second image. 89 | */ 90 | public void setSecondImageResource(int id) { 91 | setSecondImage(BitmapFactory.decodeResource(getResources(), id)); 92 | } 93 | 94 | private float mixValue; 95 | 96 | /** 97 | * Sets the current mix value. 98 | * 99 | * @param mix A value between 0 and 1 representing an empty or full 100 | * mix respectively. 101 | */ 102 | public void setMixValue(float mix) { 103 | mixValue = MathUtils.clamp(mix, 0, 1); 104 | postInvalidate(); 105 | } 106 | 107 | /** 108 | * @return The current mix value - a number between 0 and 1 representing 109 | * an empty or full mix respectively. 110 | */ 111 | public float getMixValue() { 112 | return mixValue; 113 | } 114 | 115 | public ImageMixView(Context context) { 116 | super(context); 117 | init(); 118 | } 119 | 120 | public ImageMixView(Context context, AttributeSet attrs) { 121 | super(context, attrs); 122 | init(); 123 | } 124 | 125 | public ImageMixView(Context context, AttributeSet attrs, int defStyle) { 126 | super(context, attrs, defStyle); 127 | init(); 128 | } 129 | 130 | private void init() { 131 | setMixDirection(MixDirection.VERTICAL); 132 | setMixValue(0); 133 | dst1 = new Rect(); 134 | dst2 = new Rect(); 135 | } 136 | 137 | @Override 138 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 139 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 140 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 141 | 142 | int width = MeasureSpec.getSize(widthMeasureSpec); 143 | int height = MeasureSpec.getSize(heightMeasureSpec); 144 | 145 | int imageWidth = (bitmap1 == null ? 0 : bitmap1.getWidth()); 146 | int imageHeight = (bitmap1 == null ? 0 : bitmap1.getHeight()); 147 | 148 | switch (widthMode) { 149 | case MeasureSpec.EXACTLY: 150 | break; 151 | case MeasureSpec.AT_MOST: 152 | width = Math.min(width, imageWidth); 153 | break; 154 | case MeasureSpec.UNSPECIFIED: 155 | width = imageWidth; 156 | break; 157 | } 158 | 159 | switch (heightMode) { 160 | case MeasureSpec.EXACTLY: 161 | break; 162 | case MeasureSpec.AT_MOST: 163 | height = Math.min(height, imageHeight); 164 | break; 165 | case MeasureSpec.UNSPECIFIED: 166 | height = imageHeight; 167 | break; 168 | } 169 | 170 | setMeasuredDimension(width, height); 171 | } 172 | 173 | @Override 174 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 175 | super.onLayout(changed, left, top, right, bottom); 176 | // Resize our output rectangles 177 | dst1.right = dst2.right = getWidth(); 178 | dst1.bottom = dst2.bottom = getHeight(); 179 | } 180 | 181 | @Override 182 | protected void onDraw(Canvas canvas) { 183 | super.onDraw(canvas); 184 | if (bitmap1 == null || bitmap2 == null) return; 185 | // Read the bitmask to see if this is set to run in reverse 186 | final boolean reversed = (mixDirection & MixDirection.REVERSE) > 0; 187 | // If reversed, flip the mix value 188 | final float currMix = reversed ? 1 - mixValue : mixValue; 189 | 190 | // Get the non-reverse bits 191 | switch (mixDirection & ~MixDirection.REVERSE) { 192 | case MixDirection.HORIZONTAL: 193 | // Update the input rectangles 194 | final int srcLeft = (int) (currMix * bitmap1.getWidth()); 195 | src1.left = srcLeft; 196 | src2.right = srcLeft; 197 | 198 | // Update the output rectangles 199 | final int dstLeft = (int) (currMix * getWidth()); 200 | dst1.left = dstLeft; 201 | dst2.right = dstLeft; 202 | break; 203 | case MixDirection.VERTICAL: 204 | // Update the input rectangles 205 | final int srcHeight = bitmap1.getHeight(); 206 | final int srcBottom = (int) (srcHeight - (currMix * srcHeight)); 207 | src1.bottom = srcBottom; 208 | src2.top = srcBottom; 209 | 210 | // Update the output rectangles 211 | final int dstHeight = getHeight(); 212 | final int dstBottom = (int) (dstHeight - (currMix * dstHeight)); 213 | dst1.bottom = dstBottom; 214 | dst2.top = dstBottom; 215 | break; 216 | } 217 | 218 | canvas.drawBitmap(bitmap1, src1, dst1, null); 219 | canvas.drawBitmap(bitmap2, src2, dst2, null); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/collections/ProxyObservableList.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.collections; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.raizlabs.coreutils.events.WeakDelegateListObserverListener; 6 | import com.raizlabs.coreutils.util.observable.lists.ListObserver; 7 | import com.raizlabs.coreutils.util.observable.lists.ObservableList; 8 | import com.raizlabs.coreutils.util.observable.lists.ObservableListWrapper; 9 | import com.raizlabs.coreutils.util.observable.lists.SimpleListObserver; 10 | 11 | import java.lang.ref.WeakReference; 12 | import java.util.Collection; 13 | import java.util.Iterator; 14 | import java.util.List; 15 | import java.util.ListIterator; 16 | 17 | public class ProxyObservableList implements ObservableList { 18 | 19 | //region Members 20 | private ObservableList sourceList; 21 | private SimpleListObserver listObserver; 22 | private SourceListListener sourceListListener; 23 | 24 | /** 25 | * @return The underlying source list. 26 | */ 27 | protected ObservableList getSourceList() { return sourceList; } 28 | 29 | /** 30 | * Sets the underlying source list to be the given list and binds to it. 31 | * @param sourceList The list to use as a source. 32 | */ 33 | public void setSourceList(ObservableList sourceList) { 34 | synchronized (this) { 35 | // Don't bother if we're already bound to the given list 36 | // This is a != and not !list.equals because we care about particular 37 | // instances and events over "equality". We need to make sure we are 38 | // bound to the specific instance that was passed to us and not just 39 | // one which "looks" the same. 40 | if (this.sourceList != sourceList) { 41 | if (this.sourceList != null) { 42 | this.sourceList.getListObserver().removeListener(sourceListListener); 43 | } 44 | sourceList.getListObserver().addListener(sourceListListener); 45 | this.sourceList = sourceList; 46 | this.listObserver.notifyGenericChange(); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Constructs a {@link ProxyObservableList} which is bound to the given 53 | * list. 54 | */ 55 | public ProxyObservableList(ObservableList sourceList) { 56 | // We can't have no list to proxy, so if we weren't given one, make an 57 | // empty one. 58 | if (sourceList == null) sourceList = new ObservableListWrapper<>(); 59 | 60 | this.sourceListListener = new SourceListListener<>(this); 61 | this.listObserver = new SimpleListObserver<>(); 62 | setSourceList(sourceList); 63 | } 64 | 65 | //region Inherited Methods 66 | @Override 67 | public ListObserver getListObserver() { return listObserver; } 68 | 69 | @Override 70 | public void beginTransaction() { 71 | sourceList.beginTransaction(); 72 | } 73 | 74 | @Override 75 | public void endTransaction() { 76 | sourceList.endTransaction(); 77 | } 78 | 79 | @Override 80 | public void add(int location, T object) { 81 | sourceList.add(location, object); 82 | } 83 | 84 | @Override 85 | public boolean add(T object) { 86 | return sourceList.add(object); 87 | } 88 | 89 | @Override 90 | public boolean addAll(int location, Collection collection) { 91 | return sourceList.addAll(location, collection); 92 | } 93 | 94 | @Override 95 | public boolean addAll(Collection collection) { 96 | return sourceList.addAll(collection); 97 | } 98 | 99 | @Override 100 | public void clear() { 101 | sourceList.clear(); 102 | } 103 | 104 | @Override 105 | public boolean contains(Object object) { 106 | return sourceList.contains(object); 107 | } 108 | 109 | @Override 110 | public boolean containsAll(Collection collection) { 111 | return sourceList.containsAll(collection); 112 | } 113 | 114 | @Override 115 | public T get(int location) { 116 | return sourceList.get(location); 117 | } 118 | 119 | @Override 120 | public int indexOf(Object object) { 121 | return sourceList.indexOf(object); 122 | } 123 | 124 | @Override 125 | public boolean isEmpty() { 126 | return sourceList.isEmpty(); 127 | } 128 | 129 | @NonNull 130 | @Override 131 | public Iterator iterator() { 132 | return sourceList.iterator(); 133 | } 134 | 135 | @Override 136 | public int lastIndexOf(Object object) { 137 | return sourceList.lastIndexOf(object); 138 | } 139 | 140 | @NonNull 141 | @Override 142 | public ListIterator listIterator() { 143 | return sourceList.listIterator(); 144 | } 145 | 146 | @NonNull 147 | @Override 148 | public ListIterator listIterator(int location) { 149 | return sourceList.listIterator(location); 150 | } 151 | 152 | @Override 153 | public T remove(int location) { 154 | return sourceList.remove(location); 155 | } 156 | 157 | @Override 158 | public boolean remove(Object object) { 159 | return sourceList.remove(object); 160 | } 161 | 162 | @Override 163 | public boolean removeAll(Collection collection) { 164 | return sourceList.removeAll(collection); 165 | } 166 | 167 | @Override 168 | public boolean retainAll(Collection collection) { 169 | return sourceList.retainAll(collection); 170 | } 171 | 172 | @Override 173 | public T set(int location, T object) { 174 | return sourceList.set(location, object); 175 | } 176 | 177 | @Override 178 | public int size() { 179 | return sourceList.size(); 180 | } 181 | 182 | @NonNull 183 | @Override 184 | public List subList(int start, int end) { 185 | return sourceList.subList(start, end); 186 | } 187 | 188 | @NonNull 189 | @Override 190 | public Object[] toArray() { 191 | return sourceList.toArray(); 192 | } 193 | 194 | @NonNull 195 | @Override 196 | public T1[] toArray(T1[] array) { 197 | return sourceList.toArray(array); 198 | } 199 | //endregion 200 | 201 | /** 202 | * Listener class that we send to the source list which prevents leaks of 203 | * the outer list by using a {@link WeakReference}. 204 | * @param The type of data being listened to. 205 | */ 206 | private static class SourceListListener extends WeakDelegateListObserverListener> { 207 | 208 | /** 209 | * Creates a new listener which proxies back to the given list. 210 | * @param list The list to delegate calls back to. 211 | */ 212 | public SourceListListener(ProxyObservableList list) { 213 | super(list); 214 | } 215 | 216 | @Override 217 | public void onItemRangeChanged(ListObserver observer, int startPosition, int itemCount) { 218 | ProxyObservableList list = getDelegate(observer); 219 | if (list != null) { 220 | list.listObserver.notifyItemRangeChanged(startPosition, itemCount); 221 | } 222 | } 223 | 224 | @Override 225 | public void onItemRangeInserted(ListObserver observer, int startPosition, int itemCount) { 226 | ProxyObservableList list = getDelegate(observer); 227 | if (list != null) { 228 | list.listObserver.notifyItemRangeInserted(startPosition, itemCount); 229 | } 230 | } 231 | 232 | @Override 233 | public void onItemRangeRemoved(ListObserver observer, int startPosition, int itemCount) { 234 | ProxyObservableList list = getDelegate(observer); 235 | if (list != null) { 236 | list.listObserver.notifyItemRangeRemoved(startPosition, itemCount); 237 | } 238 | } 239 | 240 | @Override 241 | public void onGenericChange(ListObserver observer) { 242 | ProxyObservableList list = getDelegate(observer); 243 | if (list != null) { 244 | list.listObserver.notifyGenericChange(); 245 | } 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/collections/FilteredList.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.collections; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.raizlabs.coreutils.functions.Predicate; 6 | import com.raizlabs.coreutils.functions.PredicateGroup; 7 | import com.raizlabs.coreutils.util.observable.lists.ListObserver; 8 | import com.raizlabs.coreutils.util.observable.lists.ObservableList; 9 | import com.raizlabs.coreutils.util.observable.lists.ObservableListWrapper; 10 | import com.raizlabs.coreutils.util.observable.lists.SimpleListObserver; 11 | import com.raizlabs.coreutils.util.observable.lists.SimpleListObserverListener; 12 | 13 | import java.lang.ref.WeakReference; 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | import java.util.Iterator; 17 | import java.util.LinkedList; 18 | import java.util.List; 19 | import java.util.ListIterator; 20 | 21 | /** 22 | * A list that contains a live filter of a given list. Does not support all list methods and will 23 | * throw an {@link UnsupportedOperationException} if an unsupported method is called 24 | * @param type of item contained in this list 25 | */ 26 | public class FilteredList implements ObservableList { 27 | 28 | //region Members 29 | private ObservableList sourceList; 30 | private PredicateGroup filters; 31 | private SimpleListObserver listObserver; 32 | 33 | /** 34 | * Local list which contains all the items which currently pass our filter 35 | */ 36 | private final List filteredList; 37 | //endregion 38 | 39 | /** 40 | * Constructs a new {@link FilteredList} based off the given source list 41 | * and filter. 42 | * 43 | * @param sourceList The list to be filtered. 44 | * @param filter The filter to apply to the list. Returning true keeps the 45 | * item in this list. 46 | */ 47 | public FilteredList(ObservableList sourceList, Predicate filter) { 48 | this(sourceList, new PredicateGroup<>(true, filter)); 49 | } 50 | 51 | /** 52 | * Constructs a new {@link FilteredList} based off the given source list 53 | * and filter. 54 | * 55 | * @param sourceList The list to be filtered. 56 | * @param filters The group of filters to apply to the list. 57 | */ 58 | public FilteredList(ObservableList sourceList, PredicateGroup filters) { 59 | this.sourceList = (sourceList == null) ? new ObservableListWrapper() : sourceList; 60 | this.filters = filters; 61 | this.filteredList = new LinkedList<>(); 62 | this.listObserver = new SimpleListObserver<>(); 63 | 64 | // Binds a SourceListListener to this filtered list 65 | new SourceListListener<>(this); 66 | update(); 67 | } 68 | 69 | /** 70 | * Adds filter to apply to source list 71 | * @param filter {@link Predicate} to add as a filter 72 | */ 73 | public void addFilter(Predicate filter) { 74 | this.filters.addPredicate(filter); 75 | update(); 76 | } 77 | 78 | /** 79 | * Removes the given filter from the filter group 80 | * @param filter {@link Predicate} to remove 81 | */ 82 | public void removeFilter(Predicate filter) { 83 | this.filters.removePredicate(filter); 84 | update(); 85 | } 86 | 87 | /** 88 | * Forces this {@link FilteredList} to re-evaluate the current filters against the source list and update 89 | * accordingly. 90 | */ 91 | public void reevaluate() { 92 | update(); 93 | } 94 | 95 | //region Inherited Methods 96 | @Override 97 | public ListObserver getListObserver() { 98 | return listObserver; 99 | } 100 | 101 | @Override 102 | public void beginTransaction() { 103 | sourceList.beginTransaction(); 104 | } 105 | 106 | @Override 107 | public void endTransaction() { 108 | sourceList.endTransaction(); 109 | } 110 | 111 | @Override 112 | public void add(int location, T object) { 113 | throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support add(int, T)"); 114 | } 115 | 116 | @Override 117 | public boolean add(T object) { 118 | return sourceList.add(object); 119 | } 120 | 121 | @Override 122 | public boolean addAll(int location, Collection collection) { 123 | throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support addAll(int, Collection)"); 124 | } 125 | 126 | @Override 127 | public boolean addAll(Collection collection) { 128 | synchronized (filteredList) { 129 | return sourceList.addAll(collection); 130 | } 131 | } 132 | 133 | @Override 134 | public void clear() { 135 | throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support clear()"); 136 | } 137 | 138 | @Override 139 | public boolean contains(Object object) { 140 | return filteredList.contains(object); 141 | } 142 | 143 | @Override 144 | public boolean containsAll(Collection collection) { 145 | return filteredList.containsAll(collection); 146 | } 147 | 148 | @Override 149 | public T get(int location) { 150 | synchronized (filteredList) { 151 | return filteredList.get(location); 152 | } 153 | } 154 | 155 | @Override 156 | public int indexOf(Object object) { 157 | return filteredList.indexOf(object); 158 | } 159 | 160 | @Override 161 | public boolean isEmpty() { 162 | return filteredList.isEmpty(); 163 | } 164 | 165 | @NonNull 166 | @Override 167 | public Iterator iterator() { 168 | return filteredList.iterator(); 169 | } 170 | 171 | @Override 172 | public int lastIndexOf(Object object) { 173 | return filteredList.lastIndexOf(object); 174 | } 175 | 176 | @NonNull 177 | @Override 178 | public ListIterator listIterator() { 179 | throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support listIterator()"); 180 | } 181 | 182 | @NonNull 183 | @Override 184 | public ListIterator listIterator(int location) { 185 | throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support listIterator(int)"); 186 | } 187 | 188 | @Override 189 | public T remove(int location) { 190 | synchronized (filteredList) { 191 | T item = null; 192 | if (location < filteredList.size()) { 193 | item = filteredList.remove(location); 194 | sourceList.remove(location); 195 | } 196 | 197 | return item; 198 | } 199 | } 200 | 201 | @Override 202 | public boolean remove(Object object) { 203 | synchronized (filteredList) { 204 | if (contains(object)) { 205 | return sourceList.remove(object); 206 | } else { 207 | // Don't remove it if it doesn't exist in this list 208 | // Even if it exists in the source list 209 | return false; 210 | } 211 | } 212 | } 213 | 214 | @Override 215 | public boolean removeAll(Collection collection) { 216 | synchronized (filteredList) { 217 | if (collection != null) { 218 | for(Object item : collection) { 219 | remove(item); 220 | } 221 | } 222 | 223 | return true; 224 | } 225 | } 226 | 227 | @Override 228 | public boolean retainAll(Collection collection) { 229 | throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support retainAll(Collection)"); 230 | } 231 | 232 | @Override 233 | public T set(int location, T object) { 234 | synchronized (filteredList) { 235 | return sourceList.set(sourceList.indexOf(filteredList.get(location)), object); 236 | } 237 | } 238 | 239 | @Override 240 | public int size() { 241 | synchronized (filteredList) { 242 | return filteredList.size(); 243 | } 244 | } 245 | 246 | @NonNull 247 | @Override 248 | public List subList(int start, int end) { 249 | synchronized (filteredList) { 250 | return filteredList.subList(start, end); 251 | } 252 | } 253 | 254 | @NonNull 255 | @Override 256 | public Object[] toArray() { 257 | synchronized (filteredList) { 258 | return filteredList.toArray(); 259 | } 260 | } 261 | 262 | @NonNull 263 | @Override 264 | public T1[] toArray(T1[] array) { 265 | synchronized (filteredList) { 266 | return filteredList.toArray(array); 267 | } 268 | } 269 | //endregion 270 | 271 | /** 272 | * Called to update the contents based on the filter and the source list. 273 | */ 274 | protected final void update() { 275 | synchronized (filteredList) { 276 | filteredList.clear(); 277 | // Copy the list to avoid potential concurrency issues 278 | // If the list is modified during this time, we've locked this 279 | // method, therefore the update will come through later and 280 | // fix this again 281 | Collection items = new ArrayList<>(sourceList); 282 | for (T item : items) { 283 | if (filters.evaluate(item)) { 284 | filteredList.add(item); 285 | } 286 | } 287 | doUpdates(filteredList); 288 | listObserver.notifyGenericChange(); 289 | } 290 | } 291 | 292 | protected void doUpdates(List itemList) { 293 | 294 | } 295 | 296 | /** 297 | * Class which watches a {@link FilteredList}'s source list for updates and 298 | * updates the {@link FilteredList}. 299 | * 300 | * @param 301 | */ 302 | private static class SourceListListener extends SimpleListObserverListener { 303 | 304 | // Keep a weak reference so we don't hang on to the FilteredList. 305 | private WeakReference> listRef; 306 | private ObservableList sourceList; 307 | 308 | public SourceListListener(FilteredList list) { 309 | listRef = new WeakReference<>(list); 310 | sourceList = list.sourceList; 311 | sourceList.getListObserver().addListener(this); 312 | } 313 | 314 | @Override 315 | public void onGenericChange(ListObserver observer) { 316 | FilteredList list = listRef.get(); 317 | if (list != null) { 318 | list.update(); 319 | } else { 320 | // We don't have a reference to the filtered list anymore 321 | // so we don't do anything. Unsubscribe. 322 | sourceList.getListObserver().removeListener(this); 323 | } 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/app/FragmentStackManagerFragment.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.app; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentTransaction; 7 | 8 | /** 9 | * Class for a {@link Fragment} that is retained and manages a stack of {@link Fragment}s 10 | */ 11 | public class FragmentStackManagerFragment extends Fragment { 12 | 13 | /* 14 | * This implementation works by tagging each fragment with a string that 15 | * matches its index in the stack. This way, we can keep adding, removing, 16 | * replacing, etc. without keeping pointers to each fragment. Since the 17 | * strings can be reproduced solely by their index, we will be able to 18 | * find any fragments later just by using the findFragmentByTag method 19 | * in the FragmentManager. Since the system takes care of recreating 20 | * fragments and restoring the fragments after orientation changes, we 21 | * will always obtain the fragment which matches our current orientation. 22 | * 23 | * All we need to keep track of is how large our stack is (the index of 24 | * the top fragment) 25 | */ 26 | 27 | //region Constants 28 | 29 | private static final String KEY_CONTAINER_ID = "containerID"; 30 | private static final String KEY_STACK_TOP = "stackTop"; 31 | 32 | //endregion Constants 33 | 34 | //region Statics 35 | 36 | /** 37 | * Creates a new {@link FragmentStackManagerFragment} which maintains a 38 | * stack inside the given container id. 39 | * @param containerID The ID of the container to commit fragments to. 40 | * @return The new {@link FragmentStackManagerFragment} instance 41 | */ 42 | public static FragmentStackManagerFragment newInstance(int containerID) { 43 | FragmentStackManagerFragment manager = new FragmentStackManagerFragment(); 44 | manager.setArguments(new Bundle()); 45 | manager.setContainerID(containerID); 46 | return manager; 47 | } 48 | 49 | //endregion Statics 50 | 51 | //region Members 52 | 53 | private int currentStackTop; 54 | 55 | private int containerID; 56 | 57 | //endregion Members 58 | 59 | //region Constructors 60 | 61 | public FragmentStackManagerFragment() { 62 | super(); 63 | currentStackTop = -1; 64 | } 65 | 66 | //endregion Constructors 67 | 68 | //region Accessors 69 | 70 | protected void setContainerID(int id) { 71 | getArguments().putInt(KEY_CONTAINER_ID, id); 72 | this.containerID = id; 73 | } 74 | 75 | //endregion Accessors 76 | 77 | //region Lifecycle 78 | 79 | @Override 80 | public void onCreate(Bundle savedInstanceState) { 81 | super.onCreate(savedInstanceState); 82 | containerID = getArguments().getInt(KEY_CONTAINER_ID); 83 | // Retain the instance across orientation changes 84 | setRetainInstance(true); 85 | } 86 | 87 | //endregion Lifecycle 88 | 89 | //region Instance Methods 90 | 91 | /** 92 | * Pops fragments until the stack is empty. 93 | */ 94 | public void clear() { 95 | FragmentTransaction transaction = getFragmentManager().beginTransaction(); 96 | while (!isEmpty()) { 97 | popWithoutAttach(transaction); 98 | } 99 | transaction.commit(); 100 | } 101 | 102 | /** 103 | * Gets the fragment currently associated with the given tag. 104 | * @param tag The tag, as returned by {@link #push(Fragment)} 105 | * @return The fragment currently associated with the given tag. 106 | */ 107 | public Fragment getFragmentByTag(String tag) { 108 | return getFragmentManager().findFragmentByTag(tag); 109 | } 110 | 111 | /** 112 | * Pushes the given fragment on the top of the stack. This becomes the 113 | * newly visible fragment, while calling {@link #pop()} will return to 114 | * whatever fragment was on top before this call. 115 | * @param fragment The {@link Fragment} to push on the top of the stack 116 | * @return The identifying tag of the new fragment which can be used 117 | * to pop back down to this fragment, assuming it hasn't been removed 118 | * or replaced. 119 | */ 120 | public String push(Fragment fragment) { 121 | FragmentTransaction transaction = getFragmentManager().beginTransaction(); 122 | String newTag = push(fragment, transaction); 123 | transaction.commit(); 124 | return newTag; 125 | } 126 | 127 | /** 128 | * Pops the current fragment off the top of the stack, only if there are 129 | * fragments underneath it to be shown. The new fragment on the top of the 130 | * stack will be shown. 131 | * @return True if there were more fragments underneath and the current 132 | * fragment was popped. False if we are already at the root or the stack 133 | * is empty. 134 | */ 135 | public boolean pop() { 136 | // If we have more than just the 0 index, we have something underneath 137 | // so do the pop 138 | if (currentStackTop > 0) { 139 | FragmentTransaction transaction = getFragmentManager().beginTransaction(); 140 | pop(transaction); 141 | transaction.commit(); 142 | return true; 143 | } else { 144 | // We're at root or have no fragments 145 | return false; 146 | } 147 | } 148 | 149 | /** 150 | * Continually pops until the given tag is reached or we end up at the root 151 | * fragment. Note that this WILL remove all fragments except the root if 152 | * the tag has already been removed and we never see it. 153 | * @param tag The tag to pop to. 154 | * @return True if we are now sitting at a fragment with the given tag, 155 | * false if we ended up at the root fragment or the stack was empty to 156 | * begin with. 157 | */ 158 | public boolean popToTag(String tag) { 159 | FragmentTransaction transaction = getFragmentManager().beginTransaction(); 160 | boolean success = popToTag(tag, transaction); 161 | transaction.commit(); 162 | 163 | return success; 164 | } 165 | 166 | /** 167 | * Continually pops until the given tag is reached or we end up at the root 168 | * fragment and then pushes the given fragment on top. 169 | * @param tag The tag to pop to. 170 | * @param fragment The fragment to push over the one at the given tag. 171 | * @return The identifying tag of the new fragment which can be used 172 | * to pop back down to this fragment, assuming it hasn't been removed 173 | * or replaced. 174 | */ 175 | public String popToTagAndPush(String tag, Fragment fragment) { 176 | FragmentTransaction transaction = getFragmentManager().beginTransaction(); 177 | popToTagWithoutAttach(tag, transaction); 178 | String newTag = push(fragment, transaction); 179 | transaction.commit(); 180 | return newTag; 181 | } 182 | 183 | /** 184 | * Replaces the fragment with the given tag with the given fragment. This 185 | * will replace fragments that are not the top, so this change may not be 186 | * visible. This likely should be used in conjunction with 187 | * {@link #popToTag(String)} 188 | * @param tag The tag of the fragment to replace which will now map to the 189 | * new fragment if this returns true. 190 | * @param fragment The {@link Fragment} to replace the existing contents 191 | * with. 192 | * @return True if there was a fragment found for the given tag and was 193 | * replaced, false if no fragment was found. 194 | */ 195 | public boolean replace(String tag, Fragment fragment) { 196 | Fragment victim = getFragmentManager().findFragmentByTag(tag); 197 | if (victim != null) { 198 | FragmentTransaction transaction = getFragmentManager().beginTransaction(); 199 | transaction.replace(containerID, fragment, tag); 200 | transaction.commit(); 201 | return true; 202 | } else { 203 | return false; 204 | } 205 | } 206 | 207 | /** 208 | * Replaces the fragment with the given tag with the given fragment. This 209 | * will replace fragments that are not the top, so this change may not be 210 | * visible. This likely should be used in conjunction with 211 | * {@link #popToTag(String)} 212 | * @param tag The tag of the fragment to replace which will now map to the 213 | * new fragment if this returns true. 214 | * @param fragment The {@link Fragment} to replace the existing contents 215 | * with. 216 | * @param inAnimationId true to allow the fragment transition to animate 217 | * with a crossfade. false for an instance change. 218 | * @return True if there was a fragment found for the given tag and was 219 | * replaced, false if no fragment was found. 220 | */ 221 | public boolean replace(String tag, Fragment fragment, int inAnimationId, int outAnimationId) { 222 | if (getFragmentManager() == null) { 223 | // If you accidentally wind up calling replace() on a fragment that's been or is being 224 | // detached from the activity, don't try to do anything. 225 | return false; 226 | } 227 | 228 | Fragment victim = getFragmentManager().findFragmentByTag(tag); 229 | if (victim != null) { 230 | FragmentTransaction transaction = getFragmentManager().beginTransaction(); 231 | transaction.setCustomAnimations(inAnimationId, outAnimationId); 232 | transaction.replace(containerID, fragment, tag); 233 | transaction.commit(); 234 | return true; 235 | } else { 236 | return false; 237 | } 238 | } 239 | 240 | /** 241 | * Forces all transactions to be committed immediately. 242 | * @see FragmentManager#executePendingTransactions() 243 | */ 244 | public void executePendingTransactions() { 245 | getFragmentManager().executePendingTransactions(); 246 | } 247 | 248 | public void restoreInstanceState(Bundle bundle) { 249 | currentStackTop = bundle.getInt(KEY_STACK_TOP); 250 | } 251 | 252 | public void saveInstanceState(Bundle bundle) { 253 | bundle.putInt(KEY_STACK_TOP, currentStackTop); 254 | } 255 | 256 | /** 257 | * Returns the tag which identifies the current index in the stack. This 258 | * can be used to "remember" a position, push some fragments on top of it, 259 | * then restore back down to it using {@link #popToTag(String)} 260 | * @return The tag which identifies the current index in the stack. 261 | */ 262 | public String getTopTag() { 263 | return getTagForIndex(currentStackTop); 264 | } 265 | 266 | /** 267 | * @return The {@link Fragment} which is currently on the top of the stack 268 | * and being displayed, or null if the stack is empty. 269 | */ 270 | public Fragment getTop() { 271 | if (!isEmpty()) { 272 | return getFragmentManager().findFragmentByTag(getTopTag()); 273 | } else { 274 | return null; 275 | } 276 | } 277 | 278 | /** 279 | * @return True if we're at the root of the stack. 280 | */ 281 | public boolean isAtRootFragment() { 282 | return currentStackTop == 0; 283 | } 284 | 285 | /** 286 | * @return True if there are no {@link Fragment}s in the stack at all. 287 | */ 288 | public boolean isEmpty() { 289 | return currentStackTop < 0; 290 | } 291 | 292 | protected String getTagForIndex(int index) { 293 | return String.format("FragmentStack%d", index); 294 | } 295 | 296 | private String push(Fragment fragment, FragmentTransaction transaction) { 297 | // Get the current top and detach it. We will likely pop back to it 298 | // later so we don't want to do a full removal of the fragment 299 | // This also means we can still obtain it by tag if need be 300 | Fragment top = getTop(); 301 | if (top != null) { 302 | transaction.detach(top); 303 | } 304 | 305 | // Add the given fragment to the next index 306 | String newTag = getTagForIndex(++currentStackTop); 307 | transaction.add(containerID, fragment, newTag); 308 | return newTag; 309 | } 310 | 311 | private void pop(FragmentTransaction transaction) { 312 | popWithoutAttach(transaction); 313 | Fragment newTop = getTop(); 314 | transaction.attach(newTop); 315 | } 316 | 317 | private void popWithoutAttach(FragmentTransaction transaction) { 318 | // Fully remove the top fragment. We're doing a pop so we can't 319 | // ever return to it 320 | transaction.remove(getTop()); 321 | 322 | // Decrement the top index and reattach the new top fragment 323 | // It was detached before by push() 324 | --currentStackTop; 325 | } 326 | 327 | private boolean popToTag(String tag, FragmentTransaction transaction) { 328 | boolean result = popToTagWithoutAttach(tag, transaction); 329 | Fragment top = getTop(); 330 | if (top != null && top.isDetached()) { 331 | transaction.attach(top); 332 | } 333 | 334 | return result; 335 | } 336 | 337 | private boolean popToTagWithoutAttach(String tag, FragmentTransaction transaction) { 338 | while (!isEmpty() && !isAtRootFragment()) { 339 | if (!getTopTag().equals(tag)) { 340 | popWithoutAttach(transaction); 341 | } else { 342 | return true; 343 | } 344 | } 345 | 346 | return false; 347 | } 348 | 349 | //endregion Instance Methods 350 | } 351 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/util/observable/lists/ObservableListWrapper.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.util.observable.lists; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Iterator; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.ListIterator; 9 | 10 | /** 11 | * Class which adapts an existing {@link List} implementation into an 12 | * {@link ObservableList} 13 | * 14 | * @param The type of item that the list contains. 15 | */ 16 | public class ObservableListWrapper implements ObservableList { 17 | 18 | /** 19 | * The list which is currently backing this adapter 20 | */ 21 | protected List underlyingList; 22 | protected SimpleListObserver listObserver; 23 | private boolean runningTransaction; 24 | private boolean transactionModified; 25 | 26 | @Override 27 | public ListObserver getListObserver() { 28 | return listObserver; 29 | } 30 | 31 | /** 32 | * Constructs a new {@link ObservableListWrapper} which contains an empty 33 | * list of data. 34 | */ 35 | public ObservableListWrapper() { 36 | this(new LinkedList()); 37 | } 38 | 39 | /** 40 | * Constructs a new {@link ObservableListWrapper} which points to the given 41 | * list of data. Note that modifying the passed list will modify the contents 42 | * of this {@link ObservableListWrapper}, but changes will go unnoticed and 43 | * the events will not be raised. Perform modifications through this 44 | * {@link ObservableListWrapper} if the events need to be raised. 45 | * 46 | * @param underlyingList The list which will back this adapter 47 | */ 48 | public ObservableListWrapper(List underlyingList) { 49 | if (underlyingList == null) underlyingList = new LinkedList(); 50 | this.underlyingList = underlyingList; 51 | this.listObserver = new SimpleListObserver(); 52 | } 53 | 54 | /** 55 | * Sets the underlying list to be used as the contents of this adapter. 56 | * Note that modifying the passed list will modify the contents 57 | * of this {@link ObservableListWrapper}, but changes will go unnoticed and 58 | * the events will not be raised. Perform modifications through this 59 | * {@link ObservableListWrapper} if the events need to be raised. 60 | * 61 | * @param list The list which will back this adapter 62 | */ 63 | public void setList(List list) { 64 | this.underlyingList = list; 65 | onGenericChange(); 66 | } 67 | 68 | 69 | /** 70 | * Updates the underlying list to reflect the contents of the given {@code list} 71 | * by replacing elements which already exist and appending those that do not. 72 | * 73 | * @param list the data to update the underlying list with. 74 | */ 75 | public void updateFromList(List list) { 76 | ArrayList updateFromList = new ArrayList(list); 77 | 78 | // Loop through the current list and find duplicate entries. 79 | // Replace them in the current list while removing them from 80 | // the list that is being loaded. 81 | ListIterator it = this.listIterator(); 82 | while (it.hasNext()) { 83 | int nextIndex = it.nextIndex(); 84 | int indexOfObjectInLoadList = updateFromList.indexOf(it.next()); 85 | if (indexOfObjectInLoadList != -1) { 86 | set(nextIndex, updateFromList.remove(indexOfObjectInLoadList)); 87 | } 88 | } 89 | 90 | // Add all remaining, non-duplicate, items 91 | addAll(updateFromList); 92 | } 93 | 94 | /** 95 | * Replaces any existing instances of the given item (as defined by {@link Object#equals(Object)}) 96 | * or appends the item to the end of the list if not found. 97 | * 98 | * @param item The item to add 99 | * @param replaceAll True to replace all instances of the item, false to only replace the first instance in the list. 100 | */ 101 | public void addToListOrReplace(T item, boolean replaceAll) { 102 | boolean foundItem = false; 103 | 104 | if (item == null) { 105 | return; 106 | } else { 107 | ListIterator it = this.listIterator(); 108 | while (it.hasNext()) { 109 | int nextIndex = it.nextIndex(); 110 | if (it.next().equals(item)) { 111 | set(nextIndex, item); 112 | 113 | if (replaceAll) { 114 | foundItem = true; 115 | } else { 116 | return; 117 | } 118 | } 119 | } 120 | } 121 | 122 | if (!foundItem) { 123 | add(item); 124 | } 125 | } 126 | 127 | /** 128 | * Removes all items from this list and adds all the given items, 129 | * effectively replacing the entire list. 130 | * 131 | * @param contents The items to set as the new contents. 132 | */ 133 | public void replaceContents(Collection contents) { 134 | this.underlyingList.clear(); 135 | this.underlyingList.addAll(contents); 136 | this.listObserver.notifyGenericChange(); 137 | 138 | } 139 | 140 | @Override 141 | public boolean add(T object) { 142 | int position = underlyingList.size(); 143 | boolean result = underlyingList.add(object); 144 | if (result) { 145 | onItemRangeInserted(position, 1); 146 | } 147 | return result; 148 | } 149 | 150 | @Override 151 | public void add(int location, T object) { 152 | underlyingList.add(location, object); 153 | onItemRangeInserted(location, 1); 154 | } 155 | 156 | @Override 157 | public boolean addAll(Collection collection) { 158 | boolean result = false; 159 | int position = underlyingList.size(); 160 | if (collection != null) { 161 | result = underlyingList.addAll(collection); 162 | } 163 | if (result) { 164 | onItemRangeInserted(position, collection.size()); 165 | } 166 | return result; 167 | } 168 | 169 | @Override 170 | public boolean addAll(int location, Collection collection) { 171 | boolean result = false; 172 | if (underlyingList != null && collection != null) { 173 | result = underlyingList.addAll(location, collection); 174 | } 175 | if (result) { 176 | onItemRangeInserted(location, collection.size()); 177 | } 178 | return result; 179 | } 180 | 181 | @Override 182 | public void clear() { 183 | int count = underlyingList.size(); 184 | underlyingList.clear(); 185 | onItemRangeRemoved(0, count); 186 | } 187 | 188 | @Override 189 | public boolean contains(Object object) { 190 | return underlyingList.contains(object); 191 | } 192 | 193 | @Override 194 | public boolean containsAll(Collection arg0) { 195 | return underlyingList.containsAll(arg0); 196 | } 197 | 198 | @Override 199 | public T get(int location) { 200 | return underlyingList.get(location); 201 | } 202 | 203 | @Override 204 | public int indexOf(Object object) { 205 | return underlyingList.indexOf(object); 206 | } 207 | 208 | @Override 209 | public boolean isEmpty() { 210 | return underlyingList.isEmpty(); 211 | } 212 | 213 | @Override 214 | public Iterator iterator() { 215 | return underlyingList.iterator(); 216 | } 217 | 218 | @Override 219 | public int lastIndexOf(Object object) { 220 | return underlyingList.lastIndexOf(object); 221 | } 222 | 223 | @Override 224 | public ListIterator listIterator() { 225 | return listIterator(0); 226 | } 227 | 228 | @Override 229 | public ListIterator listIterator(int location) { 230 | return new ObservableListWrapperIterator(location); 231 | } 232 | 233 | @Override 234 | public T remove(int location) { 235 | T result = underlyingList.remove(location); 236 | onItemRangeRemoved(location, 1); 237 | return result; 238 | } 239 | 240 | @Override 241 | public boolean remove(Object object) { 242 | int index = underlyingList.indexOf(object); 243 | if (index >= 0) { 244 | remove(index); 245 | return true; 246 | } 247 | return false; 248 | } 249 | 250 | @Override 251 | public boolean removeAll(Collection collection) { 252 | boolean result = underlyingList.removeAll(collection); 253 | if (result) { 254 | onGenericChange(); 255 | } 256 | return result; 257 | } 258 | 259 | @Override 260 | public boolean retainAll(Collection collection) { 261 | boolean result = underlyingList.retainAll(collection); 262 | if (result) { 263 | onGenericChange(); 264 | } 265 | return result; 266 | } 267 | 268 | @Override 269 | public T set(int location, T object) { 270 | T result = underlyingList.set(location, object); 271 | onItemRangeChanged(location, 1); 272 | return result; 273 | } 274 | 275 | @Override 276 | public int size() { 277 | return underlyingList.size(); 278 | } 279 | 280 | @Override 281 | public List subList(int start, int end) { 282 | return underlyingList.subList(start, end); 283 | } 284 | 285 | @Override 286 | public Object[] toArray() { 287 | return underlyingList.toArray(); 288 | } 289 | 290 | @Override 291 | public E[] toArray(E[] array) { 292 | return underlyingList.toArray(array); 293 | } 294 | 295 | @Override 296 | public String toString() { 297 | return "Observable(" + underlyingList.toString() + ")"; 298 | } 299 | 300 | protected void onItemRangeChanged(int startPosition, int itemCount) { 301 | if (tryTransactionModification()) { 302 | this.listObserver.notifyItemRangeChanged(startPosition, itemCount); 303 | } 304 | } 305 | 306 | protected void onItemRangeInserted(int startPosition, int itemCount) { 307 | if (tryTransactionModification()) { 308 | this.listObserver.notifyItemRangeInserted(startPosition, itemCount); 309 | } 310 | } 311 | 312 | protected void onItemRangeRemoved(int startPosition, int itemCount) { 313 | if (tryTransactionModification()) { 314 | this.listObserver.notifyItemRangeRemoved(startPosition, itemCount); 315 | } 316 | } 317 | 318 | protected void onGenericChange() { 319 | if (tryTransactionModification()) { 320 | this.listObserver.notifyGenericChange(); 321 | } 322 | } 323 | 324 | /** 325 | * Records a modification attempt to any currently running transaction and 326 | * returns whether the change should notify listeners. 327 | * 328 | * @return True if the modification should notify listeners, false if it 329 | * should not. 330 | */ 331 | private boolean tryTransactionModification() { 332 | if (runningTransaction) { 333 | transactionModified = true; 334 | return false; 335 | } 336 | return true; 337 | } 338 | 339 | @Override 340 | public void beginTransaction() { 341 | if (!runningTransaction) { 342 | runningTransaction = true; 343 | transactionModified = false; 344 | } else { 345 | throw new IllegalStateException("Tried to begin a transaction when one was already running!"); 346 | } 347 | } 348 | 349 | @Override 350 | public void endTransaction() { 351 | if (runningTransaction) { 352 | runningTransaction = false; 353 | if (transactionModified) { 354 | onGenericChange(); 355 | } 356 | } else { 357 | throw new IllegalStateException("Tried to end a transaction when no transaction was running!"); 358 | } 359 | } 360 | 361 | /** 362 | * Mostly copied from {@link java.util.AbstractList}.FullListIterator. 363 | * Ignoring concurrent modifications since we are not currently tracking modifications. 364 | */ 365 | private class ObservableListWrapperIterator implements java.util.ListIterator { 366 | 367 | private int position; 368 | private int lastPosition = -1; 369 | 370 | public ObservableListWrapperIterator(int start) { 371 | if (start >= 0 && start <= size()) { 372 | position = start - 1; 373 | } else { 374 | throw new IndexOutOfBoundsException(); 375 | } 376 | } 377 | 378 | @Override 379 | public void add(T object) { 380 | ObservableListWrapper.this.add(position + 1, object); 381 | position++; 382 | lastPosition = -1; 383 | } 384 | 385 | @Override 386 | public boolean hasNext() { 387 | return position + 1 < size(); 388 | } 389 | 390 | @Override 391 | public boolean hasPrevious() { 392 | return position >= 0; 393 | } 394 | 395 | @Override 396 | public T next() { 397 | T result = get(position + 1); 398 | lastPosition = ++position; 399 | return result; 400 | } 401 | 402 | @Override 403 | public int nextIndex() { 404 | return position + 1; 405 | } 406 | 407 | @Override 408 | public T previous() { 409 | T result = get(position); 410 | lastPosition = position; 411 | position--; 412 | return result; 413 | } 414 | 415 | @Override 416 | public int previousIndex() { 417 | return position; 418 | } 419 | 420 | @Override 421 | public void remove() { 422 | if (this.lastPosition == -1) { 423 | throw new IllegalStateException(); 424 | } 425 | 426 | ObservableListWrapper.this.remove(lastPosition); 427 | 428 | if (position == lastPosition) { 429 | position--; 430 | } 431 | 432 | lastPosition = -1; 433 | } 434 | 435 | @Override 436 | public void set(T object) { 437 | ObservableListWrapper.this.set(lastPosition, object); 438 | } 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /library/src/main/java/com/raizlabs/coreutils/graphics/ImageFactory.java: -------------------------------------------------------------------------------- 1 | package com.raizlabs.coreutils.graphics; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.BitmapFactory.Options; 7 | import android.graphics.drawable.BitmapDrawable; 8 | import android.graphics.drawable.Drawable; 9 | import android.view.View; 10 | 11 | import com.raizlabs.coreutils.view.ViewCompatibility; 12 | 13 | import java.io.File; 14 | 15 | /** 16 | * Class of helper methods which assist with the loading of images into memory 17 | * and views. Similar to {@link BitmapFactory}. 18 | */ 19 | public class ImageFactory { 20 | 21 | /** 22 | * Sets the background of the given {@link View} to the image in the given 23 | * {@link File}, opening the image only as large as necessary to fill 24 | * the {@link View}. This will perform scaling to match the screen density. 25 | * If a dimension of the {@link View} is 0, it will be ignored, and the 26 | * image may be opened at full size. 27 | * 28 | * @param file The {@link File} to open. 29 | * @param view The {@link View} to set the background on. 30 | * @param context The {@link Context} to use to get the screen density and 31 | * properties. 32 | * @return True if the background was set or false if the image could not 33 | * be loaded. 34 | */ 35 | public static boolean setBackground(File file, View view, Context context) { 36 | return setBackground(file.getAbsolutePath(), view, context); 37 | } 38 | 39 | /** 40 | * Sets the background of the given {@link View} to the image at the given 41 | * path, opening the image only as large as necessary to fill 42 | * the {@link View}. This will perform scaling to match the screen density. 43 | * If a dimension of the {@link View} is 0, it will be ignored, and the 44 | * image may be opened at full size. 45 | * 46 | * @param pathName The path to the file to open. 47 | * @param view The {@link View} to set the background on. 48 | * @param context The {@link Context} to use to get the screen density and 49 | * properties. 50 | * @return True if the background was set or false if the image could not 51 | * be loaded. 52 | */ 53 | public static boolean setBackground(String pathName, View view, Context context) { 54 | Bitmap bitmap = decodeFile(pathName, view, false); 55 | if (bitmap != null) { 56 | ViewCompatibility.setViewBackground(view, new BitmapDrawable(context.getResources(), bitmap)); 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | /** 63 | * Sets the background of the given {@link View} to the image in the given 64 | * {@link File}, opening the image only as large as necessary to fill 65 | * the {@link View}. This does not perform scaling. If a dimension of the 66 | * {@link View} is 0, it will be ignored, and the image may be opened at 67 | * full size. 68 | * 69 | * @param file The {@link File} to decode. 70 | * @param view The {@link View} to set the background on. 71 | * @return True if the background was set or false if the image could not 72 | * be loaded. 73 | */ 74 | public static boolean setBackground(File file, View view) { 75 | return setBackground(file.getAbsolutePath(), view); 76 | } 77 | 78 | /** 79 | * Sets the background of the given {@link View} to the image at 80 | * the given path, opening the image only as large as necessary to fill 81 | * the {@link View}. This does not perform scaling. If a dimension of the 82 | * {@link View} is 0, it will be ignored, and the image may be opened at 83 | * full size. 84 | * 85 | * @param pathName The path to the file to decode. 86 | * @param view The {@link View} to set the background on. 87 | * @return True if the background was set or false if the image could not 88 | * be loaded. 89 | */ 90 | public static boolean setBackground(String pathName, View view) { 91 | Bitmap bitmap = decodeFile(pathName, view, true); 92 | if (bitmap != null) { 93 | ViewCompatibility.setViewBackground(view, new BitmapDrawable(view.getResources(), bitmap)); 94 | return true; 95 | } 96 | return false; 97 | } 98 | 99 | /** 100 | * Creates a {@link Drawable} for the given {@link Bitmap} which will be 101 | * scaled to match the screen properties. 102 | * 103 | * @param bitmap The {@link Bitmap} to create a {@link Drawable} for. 104 | * @param context The {@link Context} to use to get the screen density and 105 | * properties. 106 | * @return The scaled {@link Drawable}. 107 | */ 108 | public static Drawable getDrawableScaled(Bitmap bitmap, Context context) { 109 | return new BitmapDrawable(context.getResources(), bitmap); 110 | } 111 | 112 | /** 113 | * Creates a {@link Drawable} for the given {@link Bitmap} which will not be 114 | * scaled. 115 | * 116 | * @param bitmap The {@link Bitmap} to create a {@link Drawable} for. 117 | * @return The unscaled {@link Drawable}. 118 | */ 119 | @SuppressWarnings("deprecation") 120 | public static Drawable getDrawableUnscaled(Bitmap bitmap) { 121 | // Deprecated to enforce usage of scaling 122 | // We don't want scaling, so ignoring the deprecation. 123 | return new BitmapDrawable(bitmap); 124 | } 125 | 126 | /** 127 | * Gets a {@link Bitmap} by decoding the given {@link File}, sized 128 | * within the dimensions of the given {@link View}. If the image is 129 | * larger than the {@link View}'s dimensions, it will be opened to fit 130 | * within these dimensions. If a dimension is 0, it will be ignored, 131 | * and the image may be opened at full size. 132 | * 133 | * @param file The {@link File} to decode. 134 | * @param view The {@link View} to constrain the image within the bounds 135 | * of. If a dimension is 0, it will be ignored, and the image may be 136 | * opened at full size. 137 | * @param dontScale If set, this sets the {@link Bitmap}'s density to 138 | * {@link Bitmap#DENSITY_NONE} to avoid scaling. 139 | * @return The decoded {@link Bitmap}. 140 | */ 141 | public static Bitmap decodeFile(File file, View view, boolean dontScale) { 142 | return decodeFile(file.getAbsolutePath(), view, dontScale); 143 | } 144 | 145 | /** 146 | * Gets a {@link Bitmap} by decoding the file at the given path, sized 147 | * within the dimensions of the given {@link View}. If the image is 148 | * larger than the {@link View}'s dimensions, it will be opened to fit 149 | * within these dimensions. 150 | * 151 | * @param pathName The path to the file to decode. 152 | * @param view The {@link View} to constrain the image within the bounds 153 | * of. If a dimension is 0, it will be ignored. 154 | * @param dontScale If set, this sets the {@link Bitmap}'s density to 155 | * {@link Bitmap#DENSITY_NONE} to avoid scaling. 156 | * @return The decoded {@link Bitmap}. 157 | */ 158 | public static Bitmap decodeFile(String pathName, View view, boolean dontScale) { 159 | int maxWidth = view.getWidth(); 160 | int maxHeight = view.getHeight(); 161 | 162 | // If we don't get a size, we may be between measure() and layout() 163 | // Attempt to grab the measured size 164 | if (maxWidth == 0) maxWidth = view.getMeasuredWidth(); 165 | if (maxHeight == 0) maxHeight = view.getMeasuredHeight(); 166 | 167 | if (maxWidth == 0) maxWidth = Integer.MAX_VALUE; 168 | if (maxHeight == 0) maxHeight = Integer.MAX_VALUE; 169 | 170 | return decodeFile(pathName, maxWidth, maxHeight, dontScale); 171 | } 172 | 173 | /** 174 | * Gets a {@link Bitmap} by decoding the given {@link File}, sized 175 | * within the given dimensions and the dimensions of the given {@link View}. 176 | * If the image is larger than the given dimensions, it will be opened to 177 | * fit within these dimensions. 178 | * 179 | * @param file The {@link File} to decode. 180 | * @param view The {@link View} to constrain the image within the bounds of. 181 | * If a dimensions is 0, it will be ignored, and the given max value will be 182 | * used. 183 | * @param maxWidth The maximum width allowed for the {@link Bitmap}. 184 | * @param maxHeight The maximum height allowed for the {@link Bitmap}. 185 | * @param dontScale If set, this sets the {@link Bitmap}'s density to 186 | * {@link Bitmap#DENSITY_NONE} to avoid scaling. 187 | * @return The decoded {@link Bitmap}. 188 | */ 189 | public static Bitmap decodeFile(File file, View view, int maxWidth, int maxHeight, boolean dontScale) { 190 | return decodeFile(file.getAbsolutePath(), view, maxWidth, maxHeight, dontScale); 191 | } 192 | 193 | /** 194 | * Gets a {@link Bitmap} by decoding the file at the given path, sized 195 | * within the given dimensions and the dimensions of the given {@link View}. 196 | * If the image is larger than the given dimensions, it will be opened to 197 | * fit within these dimensions. 198 | * 199 | * @param pathName The path to the file to decode. 200 | * @param view The {@link View} to constrain the image within the bounds of. 201 | * If a dimensions is 0, they will be ignored. 202 | * @param maxWidth The maximum width allowed for the {@link Bitmap}. 203 | * @param maxHeight The maximum height allowed for the {@link Bitmap}. 204 | * @param dontScale If set, this sets the {@link Bitmap}'s density to 205 | * {@link Bitmap#DENSITY_NONE} to avoid scaling. 206 | * @return The decoded {@link Bitmap}. 207 | */ 208 | public static Bitmap decodeFile(String pathName, View view, int maxWidth, int maxHeight, boolean dontScale) { 209 | final int viewWidth = view.getWidth(); 210 | final int viewHeight = view.getHeight(); 211 | if (viewWidth > 0) 212 | maxWidth = java.lang.Math.min(viewWidth, maxWidth); 213 | 214 | if (viewHeight > 0) 215 | maxHeight = java.lang.Math.min(viewHeight, maxHeight); 216 | 217 | return decodeFile(pathName, maxWidth, maxHeight, dontScale); 218 | } 219 | 220 | /** 221 | * Gets a {@link Bitmap} by decoding the given {@link File}, sized 222 | * within the given dimensions. If the image is larger than the max 223 | * dimensions, it will be opened to fit within these dimensions. 224 | * 225 | * @param file The {@link File} to decode. 226 | * @param maxWidth The maximum width allowed for the {@link Bitmap}. 227 | * @param maxHeight The maximum height allowed for the {@link Bitmap}. 228 | * @param dontScale If set, this sets the {@link Bitmap}'s density to 229 | * {@link Bitmap#DENSITY_NONE} to avoid scaling. 230 | * @return The decoded {@link Bitmap}. 231 | */ 232 | public static Bitmap decodeFile(File file, int maxWidth, int maxHeight, boolean dontScale) { 233 | return decodeFile(file.getAbsolutePath(), maxWidth, maxHeight, dontScale); 234 | } 235 | 236 | /** 237 | * Gets a {@link Bitmap} by decoding the file at the given path, sized 238 | * within the given dimensions. If the image is larger than the max 239 | * dimensions, it will be opened to fit within these dimensions. 240 | * 241 | * @param pathName The path to the file to decode. 242 | * @param maxWidth The maximum width allowed for the {@link Bitmap}. 243 | * @param maxHeight The maximum height allowed for the {@link Bitmap}. 244 | * @param dontScale If set, this sets the {@link Bitmap}'s density to 245 | * {@link Bitmap#DENSITY_NONE} to avoid scaling. 246 | * @return The decoded {@link Bitmap}. 247 | */ 248 | public static Bitmap decodeFile(String pathName, int maxWidth, int maxHeight, boolean dontScale) { 249 | return decodeFile(pathName, maxWidth, maxHeight, dontScale, null); 250 | } 251 | 252 | /** 253 | * Gets a {@link Bitmap} by decoding the file at the given path, sized 254 | * within the given dimensions. If the image is larger than the max 255 | * dimensions, it will be opened to fit within these dimensions. 256 | * 257 | * @param pathName The path to the file to decode. 258 | * @param maxWidth The maximum width allowed for the {@link Bitmap}. 259 | * @param maxHeight The maximum height allowed for the {@link Bitmap}. 260 | * @param dontScale If set, this sets the {@link Bitmap}'s density to 261 | * {@link Bitmap#DENSITY_NONE} to avoid scaling. 262 | * @param options Options to use to decode the file. 263 | * @return The decoded {@link Bitmap}. 264 | */ 265 | public static Bitmap decodeFile(String pathName, int maxWidth, int maxHeight, boolean dontScale, Options options) { 266 | Bitmap bitmap; 267 | if (options == null) options = new Options(); 268 | if (maxWidth < Integer.MAX_VALUE || maxHeight < Integer.MAX_VALUE) { 269 | options.inJustDecodeBounds = true; 270 | BitmapFactory.decodeFile(pathName, options); 271 | 272 | options.inSampleSize = getSampleSize(options, maxWidth, maxHeight); 273 | options.inJustDecodeBounds = false; 274 | bitmap = BitmapFactory.decodeFile(pathName, options); 275 | } else { 276 | bitmap = BitmapFactory.decodeFile(pathName, options); 277 | } 278 | if (dontScale && bitmap != null) { 279 | bitmap.setDensity(Bitmap.DENSITY_NONE); 280 | } 281 | return bitmap; 282 | } 283 | 284 | /** 285 | * Gets the sample size to use to constrain the image to the given maximum 286 | * dimensions. 287 | * 288 | * @param options The decoded {@link Options} from decoding the file. 289 | * @param maxWidth The maximum width allowed. 290 | * @param maxHeight The maximum height allowed. 291 | * @return The sample size to use to keep within the bounds. This is rounded 292 | * so it may not be exactly within the bounds. 293 | */ 294 | public static int getSampleSize(Options options, int maxWidth, int maxHeight) { 295 | final int height = options.outHeight; 296 | final int width = options.outWidth; 297 | int inSampleSize = 1; 298 | 299 | if (height > maxHeight || width > maxWidth) { 300 | final int heightMult = height / maxHeight; 301 | final int widthMult = width / maxWidth; 302 | if (widthMult < heightMult) { 303 | inSampleSize = java.lang.Math.round((float) height / (float) maxHeight); 304 | } else { 305 | inSampleSize = java.lang.Math.round((float) width / (float) maxWidth); 306 | } 307 | } 308 | return inSampleSize; 309 | } 310 | 311 | /** 312 | * Decodes the given file, obtaining only the image size instead of decoding 313 | * the whole image. 314 | * 315 | * @param pathName The path to the file to decode. 316 | * @return The populated {@link Options} containing the size in 317 | * {@link Options#outWidth} and {@link Options#outHeight}. 318 | */ 319 | public static Options decodeFileSize(String pathName) { 320 | Options options = new Options(); 321 | options.inJustDecodeBounds = true; 322 | BitmapFactory.decodeFile(pathName, options); 323 | return options; 324 | } 325 | } 326 | --------------------------------------------------------------------------------