├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── demo ├── build.gradle ├── proguard-rules.txt └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── telly │ │ └── floatingaction │ │ └── demo │ │ └── MainActivity.java │ └── res │ ├── drawable-hdpi │ ├── ic_action_about.png │ └── ic_action_overflow.png │ ├── drawable-mdpi │ ├── ic_action_about.png │ └── ic_action_overflow.png │ ├── drawable-xhdpi │ ├── ic_action_about.png │ └── ic_action_overflow.png │ ├── drawable-xxhdpi │ ├── ic_action_about.png │ └── ic_action_overflow.png │ ├── drawable-xxxhdpi │ └── ic_action_overflow.png │ ├── layout │ ├── activity_main.xml │ └── list_item.xml │ ├── menu │ └── main_menu.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── extras └── background.sketch │ ├── Data │ ├── metadata │ └── version ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── build.gradle ├── gradle-mvn-push.gradle ├── gradle.properties ├── proguard-rules.txt └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── telly │ │ └── floatingaction │ │ ├── ChattyListView.java │ │ └── FloatingAction.java │ └── res │ ├── drawable-hdpi │ ├── fa_default_background_default.png │ └── fa_default_background_pressed.png │ ├── drawable-mdpi │ ├── fa_default_background_default.png │ └── fa_default_background_pressed.png │ ├── drawable-xhdpi │ ├── fa_default_background_default.png │ └── fa_default_background_pressed.png │ ├── drawable-xxhdpi │ ├── fa_default_background_default.png │ └── fa_default_background_pressed.png │ ├── drawable-xxxhdpi │ ├── fa_default_background_default.png │ └── fa_default_background_pressed.png │ ├── drawable │ └── fa_default_background.xml │ └── layout │ └── fa_action_layout.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by http://www.gitignore.io 2 | 3 | ### Android ### 4 | # Built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | .gradletasknamecache 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | #Log Files 30 | *.log 31 | 32 | 33 | ### Intellij ### 34 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 35 | 36 | ## Directory-based project format 37 | .idea/ 38 | # if you remove the above rule, at least ignore user-specific stuff: 39 | # .idea/workspace.xml 40 | # .idea/tasks.xml 41 | # and these sensitive or high-churn files: 42 | # .idea/dataSources.ids 43 | # .idea/dataSources.xml 44 | # .idea/sqlDataSources.xml 45 | # .idea/dynamic.xml 46 | 47 | ## File-based project format 48 | *.ipr 49 | *.iml 50 | *.iws 51 | 52 | ## Additional for IntelliJ 53 | out/ 54 | 55 | # generated by mpeltonen/sbt-idea plugin 56 | .idea_modules/ 57 | 58 | # generated by JIRA plugin 59 | atlassian-ide-plugin.xml 60 | 61 | # generated by Crashlytics plugin (for Android Studio and Intellij) 62 | com_crashlytics_export_strings.xml 63 | 64 | 65 | ### Gradle ### 66 | .gradle 67 | build/ 68 | 69 | # Ignore Gradle GUI config 70 | gradle-app.setting 71 | 72 | 73 | ### Eclipse ### 74 | *.pydevproject 75 | .metadata 76 | .gradle 77 | bin/ 78 | tmp/ 79 | *.tmp 80 | *.bak 81 | *.swp 82 | *~.nib 83 | local.properties 84 | .settings/ 85 | .loadpath 86 | 87 | # External tool builders 88 | .externalToolBuilders/ 89 | 90 | # Locally stored "Eclipse launch configurations" 91 | *.launch 92 | 93 | # CDT-specific 94 | .cproject 95 | 96 | # PDT-specific 97 | .buildpath 98 | 99 | # sbteclipse plugin 100 | .target 101 | 102 | # TeXlipse plugin 103 | .texlipse 104 | 105 | 106 | ### OSX ### 107 | .DS_Store 108 | .AppleDouble 109 | .LSOverride 110 | 111 | # Icon must end with two \r 112 | Icon 113 | 114 | 115 | 116 | # Thumbnails 117 | ._* 118 | 119 | # Files that might appear on external disk 120 | .Spotlight-V100 121 | .Trashes 122 | 123 | # Directories potentially created on remote AFP share 124 | .AppleDB 125 | .AppleDesktop 126 | Network Trash Folder 127 | Temporary Items 128 | .apdisk 129 | 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Telly, Inc. and other contributors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to permit 8 | persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 20 | THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FloatingAction 2 | ============== 3 | 4 | A basic implementation of Floating Action Button pattern as seen on Material Design 5 | 6 | ![Le demo](http://i.imgur.com/Z0nTwvj.gif) 7 | 8 | ### Demo 9 | 10 | [![FloatingAction Demo on Google Play Store](http://developer.android.com/images/brand/en_generic_rgb_wo_60.png)](https://play.google.com/store/apps/details?id=com.telly.floatingaction.demo) 11 | 12 | ### Usage 13 | 14 | See demo, at this point latest version is `0.0.6` 15 | 16 | ```groovy 17 | compile 'com.telly:floatingaction:(insert latest version)' 18 | ``` 19 | 20 | ```java 21 | mFloatingAction = FloatingAction.from(this) 22 | .listenTo(mListView) 23 | .icon(R.drawable.ic_action_about) 24 | .listener(this) 25 | .build(); 26 | ``` 27 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:0.12.+' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | version = VERSION_NAME 17 | group = GROUP 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android' 2 | 3 | dependencies { 4 | compile project(':library') 5 | } 6 | 7 | android { 8 | compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) 9 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 10 | 11 | defaultConfig { 12 | applicationId 'com.telly.floatingaction.demo' 13 | minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION) 14 | targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) 15 | versionName project.VERSION_NAME 16 | versionCode Integer.parseInt(project.VERSION_CODE) 17 | } 18 | 19 | buildTypes { 20 | release { 21 | runProguard false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demo/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /opt/homebrew-cask/Caskroom/android-studio-bundle/0.4.2 build-133.970939/Android Studio.app/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 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /demo/src/main/java/com/telly/floatingaction/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.telly.floatingaction.demo; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.Menu; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.BaseAdapter; 10 | import android.widget.ListAdapter; 11 | import android.widget.ListView; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import com.telly.floatingaction.FloatingAction; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Random; 20 | 21 | public class MainActivity extends Activity implements View.OnClickListener { 22 | private ListView mListView; 23 | private FloatingAction mFloatingAction; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_main); 29 | 30 | mListView = (ListView) findViewById(android.R.id.list); 31 | 32 | final ListAdapter adapter = new LeAdapter(this, generateMockItems()); 33 | mListView.setAdapter(adapter); 34 | 35 | mFloatingAction = FloatingAction.from(this) 36 | .listenTo(mListView) 37 | .colorResId(R.color.branding) 38 | .icon(R.drawable.ic_action_about) 39 | .listener(this) 40 | .build(); 41 | 42 | getWindow().setBackgroundDrawable(null); 43 | } 44 | 45 | @Override 46 | public void onClick(View v) { 47 | Toast.makeText(this, "Floating Action Clicked", Toast.LENGTH_SHORT).show(); 48 | } 49 | 50 | @Override 51 | public boolean onCreateOptionsMenu(Menu menu) { 52 | getMenuInflater().inflate(R.menu.main_menu, menu); 53 | return super.onCreateOptionsMenu(menu); 54 | } 55 | 56 | @Override 57 | protected void onDestroy() { 58 | super.onDestroy(); 59 | 60 | mFloatingAction.onDestroy(); 61 | } 62 | 63 | 64 | static class LeAdapter extends BaseAdapter { 65 | private final Activity mActivity; 66 | private final LayoutInflater mInflater; 67 | private final List mItems; 68 | 69 | public LeAdapter(Activity activity, List items) { 70 | mActivity = activity; 71 | mInflater = activity.getLayoutInflater(); 72 | mItems = items; 73 | } 74 | 75 | @Override 76 | public int getCount() { 77 | return mItems.size(); 78 | } 79 | 80 | @Override 81 | public Item getItem(int position) { 82 | return mItems.get(position); 83 | } 84 | 85 | @Override 86 | public long getItemId(int position) { 87 | return position; 88 | } 89 | 90 | @Override 91 | public View getView(int position, View convertView, ViewGroup parent) { 92 | if (convertView == null) { 93 | convertView = mInflater.inflate(R.layout.list_item, parent, false); 94 | } 95 | final Item item = getItem(position); 96 | ViewHolder.from(convertView).bind(item); 97 | return convertView; 98 | } 99 | 100 | static class ViewHolder { 101 | final TextView mTxt; 102 | 103 | ViewHolder(View view) { 104 | mTxt = (TextView)view; 105 | } 106 | 107 | void bind(Item item) { 108 | mTxt.setBackgroundResource(item.mBg); 109 | mTxt.setText(item.mTxt); 110 | } 111 | 112 | static ViewHolder from(View view) { 113 | final Object tag = view.getTag(); 114 | if (tag instanceof ViewHolder) { 115 | return (ViewHolder) tag; 116 | } 117 | return new ViewHolder(view); 118 | } 119 | } 120 | } 121 | 122 | static List generateMockItems() { 123 | final int n = 100; 124 | final List items = new ArrayList(n); 125 | int bg = 0; 126 | for (int i = 0; i < n; i++) { 127 | bg = nextBg(bg, 0); 128 | items.add(new Item(bg, "Item " + i)); 129 | } 130 | return items; 131 | } 132 | 133 | static final Random sRandom = new Random(System.currentTimeMillis()); 134 | static final int[] sBgs = { 135 | R.color.turquoise, 136 | R.color.green_sea, 137 | R.color.emerland, 138 | R.color.nephritis, 139 | R.color.peter_river, 140 | R.color.belize_hole, 141 | R.color.amethyst, 142 | R.color.wisteria, 143 | R.color.wet_asphalt, 144 | R.color.midnight_blue, 145 | R.color.sun_flower, 146 | R.color.orange, 147 | R.color.carrot, 148 | R.color.pumpkin, 149 | R.color.alizarin, 150 | R.color.pomegranate 151 | }; 152 | static int nextBg(int current, int control) { 153 | final int i = sRandom.nextInt(sBgs.length); 154 | final int candidate = sBgs[i]; 155 | if (current == candidate && control < 3) { 156 | return nextBg(current, ++control); 157 | } 158 | return candidate; 159 | } 160 | 161 | static class Item { 162 | final int mBg; 163 | final String mTxt; 164 | 165 | Item(int mBg, String mTxt) { 166 | this.mBg = mBg; 167 | this.mTxt = mTxt; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/ic_action_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/demo/src/main/res/drawable-hdpi/ic_action_about.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/ic_action_overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/demo/src/main/res/drawable-hdpi/ic_action_overflow.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-mdpi/ic_action_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/demo/src/main/res/drawable-mdpi/ic_action_about.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-mdpi/ic_action_overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/demo/src/main/res/drawable-mdpi/ic_action_overflow.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_action_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/demo/src/main/res/drawable-xhdpi/ic_action_about.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_action_overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/demo/src/main/res/drawable-xhdpi/ic_action_overflow.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_action_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/demo/src/main/res/drawable-xxhdpi/ic_action_about.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_action_overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/demo/src/main/res/drawable-xxhdpi/ic_action_overflow.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxxhdpi/ic_action_overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/demo/src/main/res/drawable-xxxhdpi/ic_action_overflow.png -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | -------------------------------------------------------------------------------- /demo/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /demo/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /demo/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffa4c639 4 | #ff1ABC9C 5 | #ff16A085 6 | #ff2ECC71 7 | #ff27AE60 8 | #ff3498DB 9 | #ff2980B9 10 | #ff9B59B6 11 | #ff8E44AD 12 | #ff34495E 13 | #ff2C3E50 14 | #ffF1C40F 15 | #ffF39C12 16 | #ffE67E22 17 | #ffD35400 18 | #ffE74C3C 19 | #ffC0392B 20 | -------------------------------------------------------------------------------- /demo/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Floating Action Demo 4 | Floating Action 5 | About 6 | Settings 7 | 8 | -------------------------------------------------------------------------------- /demo/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /extras/background.sketch/Data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/extras/background.sketch/Data -------------------------------------------------------------------------------- /extras/background.sketch/metadata: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | app 6 | com.bohemiancoding.sketch3 7 | build 8 | 7892 9 | commit 10 | a6af16d03e62e9b34da1be1098affa7b56d9838b 11 | fonts 12 | 13 | length 14 | 17157 15 | version 16 | 37 17 | 18 | 19 | -------------------------------------------------------------------------------- /extras/background.sketch/version: -------------------------------------------------------------------------------- 1 | 37 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=0.0.6 2 | VERSION_CODE=6 3 | GROUP=com.telly 4 | 5 | POM_DESCRIPTION=A basic implementation of Floating Action pattern 6 | POM_URL=https://github.com/telly/FloatingAction 7 | POM_SCM_URL=https://github.com/telly/FloatingAction 8 | POM_SCM_CONNECTION=scm:git@github.com:telly/FloatingAction.git 9 | POM_SCM_DEV_CONNECTION=scm:git@github.com:telly/FloatingAction.git 10 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 11 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 12 | POM_LICENCE_DIST=repo 13 | POM_DEVELOPER_ID=eveliotc 14 | POM_DEVELOPER_NAME=Evelio Tarazona Caceres 15 | 16 | ANDROID_BUILD_MIN_SDK_VERSION=14 17 | ANDROID_BUILD_TARGET_SDK_VERSION=19 18 | ANDROID_BUILD_SDK_VERSION=19 19 | ANDROID_BUILD_TOOLS_VERSION=20 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jun 30 17:43:21 PDT 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android-library' 2 | 3 | dependencies { 4 | compile 'com.android.support:support-annotations:20.0.0' 5 | } 6 | 7 | android { 8 | compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) 9 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 10 | 11 | defaultConfig { 12 | minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION) 13 | targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) 14 | versionName project.VERSION_NAME 15 | versionCode Integer.parseInt(project.VERSION_CODE) 16 | } 17 | 18 | buildTypes { 19 | release { 20 | runProguard false 21 | proguardFiles getDefaultProguardFile('proguard-android.pro'), 'proguard-rules.txt' 22 | } 23 | } 24 | } 25 | 26 | apply from: './gradle-mvn-push.gradle' -------------------------------------------------------------------------------- /library/gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | def isReleaseBuild() { 21 | return VERSION_NAME.contains("SNAPSHOT") == false 22 | } 23 | 24 | def getReleaseRepositoryUrl() { 25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 27 | } 28 | 29 | def getSnapshotRepositoryUrl() { 30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 31 | : "https://oss.sonatype.org/content/repositories/snapshots/" 32 | } 33 | 34 | def getRepositoryUsername() { 35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 36 | } 37 | 38 | def getRepositoryPassword() { 39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 40 | } 41 | 42 | afterEvaluate { project -> 43 | uploadArchives { 44 | repositories { 45 | mavenDeployer { 46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 47 | 48 | pom.groupId = GROUP 49 | pom.artifactId = POM_ARTIFACT_ID 50 | pom.version = VERSION_NAME 51 | 52 | repository(url: getReleaseRepositoryUrl()) { 53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 54 | } 55 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | 59 | pom.project { 60 | name POM_NAME 61 | packaging POM_PACKAGING 62 | description POM_DESCRIPTION 63 | url POM_URL 64 | 65 | scm { 66 | url POM_SCM_URL 67 | connection POM_SCM_CONNECTION 68 | developerConnection POM_SCM_DEV_CONNECTION 69 | } 70 | 71 | licenses { 72 | license { 73 | name POM_LICENCE_NAME 74 | url POM_LICENCE_URL 75 | distribution POM_LICENCE_DIST 76 | } 77 | } 78 | 79 | developers { 80 | developer { 81 | id POM_DEVELOPER_ID 82 | name POM_DEVELOPER_NAME 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | signing { 91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 92 | sign configurations.archives 93 | } 94 | 95 | task androidJavadocs(type: Javadoc) { 96 | source = android.sourceSets.main.java 97 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 98 | } 99 | 100 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 101 | classifier = 'javadoc' 102 | from androidJavadocs.destinationDir 103 | } 104 | 105 | task androidSourcesJar(type: Jar) { 106 | classifier = 'sources' 107 | from android.sourceSets.main.java 108 | } 109 | 110 | artifacts { 111 | archives androidSourcesJar 112 | archives androidJavadocsJar 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=FloatingAction Library 2 | POM_ARTIFACT_ID=floatingaction 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /library/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 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 | #} -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/src/main/java/com/telly/floatingaction/ChattyListView.java: -------------------------------------------------------------------------------- 1 | package com.telly.floatingaction; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.view.View; 7 | import android.widget.AbsListView; 8 | import android.widget.ListView; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * A list view who likes to be listened by many 15 | */ 16 | public class ChattyListView extends ListView { 17 | 18 | private List mScrollListeners; 19 | private List mTouchListeners; 20 | 21 | public ChattyListView(Context context) { 22 | super(context); 23 | listen(); 24 | } 25 | 26 | public ChattyListView(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | listen(); 29 | } 30 | 31 | public ChattyListView(Context context, AttributeSet attrs, int defStyle) { 32 | super(context, attrs, defStyle); 33 | listen(); 34 | } 35 | 36 | private void listen() { 37 | super.setOnScrollListener(mInternalListener); 38 | super.setOnTouchListener(mInternalListener); 39 | } 40 | 41 | @Override 42 | public void setOnScrollListener(OnScrollListener listener) { 43 | addOnScrollListener(listener); 44 | } 45 | 46 | @Override 47 | public void setOnTouchListener(OnTouchListener listener) { 48 | addOnTouchListener(listener); 49 | } 50 | 51 | public void addOnTouchListener(OnTouchListener listener) { 52 | if (listener == null) { 53 | throw new NullPointerException("Invalid listener provided."); 54 | } 55 | if (mTouchListeners == null) { 56 | mTouchListeners = new ArrayList(); 57 | } 58 | 59 | if (!mTouchListeners.contains(listener)) { 60 | mTouchListeners.add(listener); 61 | } 62 | } 63 | 64 | public void addOnScrollListener(OnScrollListener listener) { 65 | if (listener == null) { 66 | throw new NullPointerException("Invalid listener provided."); 67 | } 68 | if (mScrollListeners == null) { 69 | mScrollListeners = new ArrayList(); 70 | } 71 | 72 | if (!mScrollListeners.contains(listener)) { 73 | mScrollListeners.add(listener); 74 | } 75 | } 76 | 77 | public void removeOnTouchListener(OnTouchListener listener) { 78 | if (mTouchListeners == null) { 79 | return; 80 | } 81 | mTouchListeners.remove(listener); 82 | } 83 | 84 | public void removeOnScrollListener(OnScrollListener listener) { 85 | if (mScrollListeners == null) { 86 | return; 87 | } 88 | mScrollListeners.remove(listener); 89 | } 90 | 91 | private final MultiListener mInternalListener = new MultiListener() { 92 | @Override 93 | public void onScrollStateChanged(AbsListView view, int scrollState) { 94 | if (mScrollListeners == null) { 95 | return; 96 | } 97 | for (int i = 0, count = mScrollListeners.size(); i < count; i++) { 98 | mScrollListeners.get(i).onScrollStateChanged(view, scrollState); 99 | } 100 | } 101 | 102 | @Override 103 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 104 | if (mScrollListeners == null) { 105 | return; 106 | } 107 | for (int i = 0, count = mScrollListeners.size(); i < count; i++) { 108 | mScrollListeners.get(i).onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); 109 | } 110 | } 111 | 112 | @Override 113 | public boolean onTouch(View v, MotionEvent event) { 114 | if (mTouchListeners == null) { 115 | return false; 116 | } 117 | boolean result = false; 118 | for (int i = 0, count = mTouchListeners.size(); i < count; i++) { 119 | if (mTouchListeners.get(i).onTouch(v, event)) { 120 | result = true; 121 | } 122 | } 123 | return result; 124 | } 125 | }; 126 | 127 | private interface MultiListener extends OnScrollListener, OnTouchListener { 128 | 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /library/src/main/java/com/telly/floatingaction/FloatingAction.java: -------------------------------------------------------------------------------- 1 | package com.telly.floatingaction; 2 | 3 | import android.animation.TimeInterpolator; 4 | import android.app.Activity; 5 | import android.graphics.PorterDuff; 6 | import android.graphics.drawable.Drawable; 7 | import android.support.annotation.ColorRes; 8 | import android.support.annotation.DrawableRes; 9 | import android.support.annotation.IdRes; 10 | import android.support.annotation.NonNull; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.ViewTreeObserver; 14 | import android.view.animation.AccelerateDecelerateInterpolator; 15 | import android.widget.AbsListView; 16 | import android.widget.ImageButton; 17 | 18 | import static android.view.View.OnClickListener; 19 | import static android.view.ViewTreeObserver.OnPreDrawListener; 20 | import static android.widget.AbsListView.OnScrollListener; 21 | import static com.telly.floatingaction.FloatingAction.Utils.checkNotNull; 22 | import static com.telly.floatingaction.FloatingAction.Utils.checkResId; 23 | 24 | /** 25 | * An action stolen from ActionBar which happens to float 26 | */ 27 | public class FloatingAction { 28 | private Activity mActivity; 29 | private ViewGroup mViewGroup; 30 | private ImageButton mView; 31 | private AbsListView mAbsListView; 32 | private TimeInterpolator mInterpolator; 33 | private boolean mHide; 34 | 35 | private Delegate mDelegate = new Delegate(); 36 | private PublicListener mPublicListener; 37 | private long mDuration; 38 | 39 | public static Builder from(Activity activity) { 40 | return new Builder(activity); 41 | } 42 | 43 | private FloatingAction(Builder builder) { 44 | mActivity = builder.mActivity; 45 | 46 | mInterpolator = builder.mInterpolator; 47 | mDuration = builder.mDuration; 48 | 49 | if (builder.mParent != null) { 50 | mViewGroup = builder.mParent; 51 | } else { 52 | mViewGroup = (ViewGroup) mActivity.findViewById(builder.mTargetParentId); 53 | checkNotNull(mViewGroup, "No parent found with id " + builder.mTargetParentId); 54 | } 55 | 56 | final View parent = mActivity.getLayoutInflater().inflate(R.layout.fa_action_layout, mViewGroup, true); 57 | mView = (ImageButton) parent.findViewById(R.id.fa_action_view); 58 | 59 | // Setup drawable 60 | Drawable icon = builder.mIcon; 61 | checkNotNull(icon, "Menu item must provide a drawable"); 62 | icon = icon.mutate(); 63 | icon.setColorFilter(builder.mIconColor, PorterDuff.Mode.MULTIPLY); 64 | mView.setImageDrawable(icon); 65 | mView.setOnClickListener(builder.mClickListener); 66 | // Start listening if any 67 | listenTo(builder.mAbsListView); 68 | } 69 | 70 | public void listenTo(AbsListView absListView) { 71 | final AbsListView currentAbsListView = mAbsListView; 72 | if (currentAbsListView instanceof ChattyListView) { 73 | ((ChattyListView) currentAbsListView).removeOnScrollListener(mDelegate); 74 | } 75 | mAbsListView = absListView; 76 | if (mAbsListView != null) { 77 | mDelegate.reset(); 78 | mAbsListView.setOnScrollListener(mDelegate); 79 | } 80 | } 81 | 82 | /** 83 | * Obtain an {@link OnScrollListener} instance which can be used as plan B 84 | * when there is no access to an instance of {@link AbsListView}. 85 | * Note: Usage of {@link #listenTo(android.widget.AbsListView)} is always preferred. 86 | * 87 | * @returns {@link OnScrollListener} to provide other {@link AbsListView} wrapper views 88 | */ 89 | public OnScrollListener getOnScrollListener() { 90 | if (mPublicListener == null) { 91 | mPublicListener = new PublicListener(mDelegate); 92 | } 93 | return mPublicListener; 94 | } 95 | 96 | public void onDestroy() { 97 | listenTo(null); 98 | mView.setOnClickListener(null); 99 | mViewGroup.removeView(mView); 100 | mViewGroup = null; 101 | mView = null; 102 | mActivity = null; 103 | if (mPublicListener != null) { 104 | mPublicListener.destroy(); 105 | mPublicListener = null; 106 | } 107 | } 108 | 109 | private void onDirectionChanged(boolean goingDown) { 110 | leHide(goingDown, true); 111 | } 112 | 113 | public void hide() { 114 | hide(true); 115 | } 116 | 117 | public void hide(boolean animate) { 118 | leHide(true, animate); 119 | } 120 | 121 | public void show() { 122 | show(true); 123 | } 124 | 125 | public void show(boolean animate) { 126 | leHide(false, animate); 127 | } 128 | 129 | private void leHide(final boolean hide, final boolean animated) { 130 | leHide(hide, animated, false); 131 | } 132 | 133 | private void leHide(final boolean hide, final boolean animated, final boolean deferred) { 134 | if (mHide != hide || deferred) { 135 | mHide = hide; 136 | final int height = mView.getHeight(); 137 | if (height == 0 && !deferred) { 138 | // Dang it, haven't been drawn before, defer! defer! 139 | final ViewTreeObserver vto = mView.getViewTreeObserver(); 140 | if (vto.isAlive()) { 141 | vto.addOnPreDrawListener(new OnPreDrawListener() { 142 | @Override 143 | public boolean onPreDraw() { 144 | // Sometimes is not the same we used to know 145 | final ViewTreeObserver currentVto = mView.getViewTreeObserver(); 146 | if (currentVto.isAlive()) { 147 | currentVto.removeOnPreDrawListener(this); 148 | } 149 | leHide(hide, animated, true); 150 | return true; 151 | } 152 | }); 153 | return; 154 | } 155 | } 156 | int marginBottom = 0; 157 | final ViewGroup.LayoutParams layoutParams = mView.getLayoutParams(); 158 | if (layoutParams instanceof ViewGroup.MarginLayoutParams) { 159 | marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin; 160 | } 161 | final int translationY = mHide ? height + marginBottom : 0; 162 | if (animated) { 163 | mView.animate() 164 | .setInterpolator(mInterpolator) 165 | .setDuration(mDuration) 166 | .translationY(translationY); 167 | } else { 168 | mView.setTranslationY(translationY); 169 | } 170 | } 171 | } 172 | 173 | class Delegate implements OnScrollListener { 174 | private static final int DIRECTION_CHANGE_THRESHOLD = 1; 175 | private int mPrevPosition; 176 | private int mPrevTop; 177 | private boolean mUpdated; 178 | 179 | @Override 180 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 181 | final View topChild = view.getChildAt(0); 182 | int firstViewTop = 0; 183 | if (topChild != null) { 184 | firstViewTop = topChild.getTop(); 185 | } 186 | boolean goingDown; 187 | boolean changed = true; 188 | if (mPrevPosition == firstVisibleItem) { 189 | final int topDelta = mPrevTop - firstViewTop; 190 | goingDown = firstViewTop < mPrevTop; 191 | changed = Math.abs(topDelta) > DIRECTION_CHANGE_THRESHOLD; 192 | } else { 193 | goingDown = firstVisibleItem > mPrevPosition; 194 | } 195 | if (changed && mUpdated) { 196 | onDirectionChanged(goingDown); 197 | } 198 | mPrevPosition = firstVisibleItem; 199 | mPrevTop = firstViewTop; 200 | mUpdated = true; 201 | } 202 | 203 | @Override 204 | public void onScrollStateChanged(AbsListView view, int scrollState) { 205 | //No-op 206 | } 207 | 208 | public void reset() { 209 | mPrevPosition = 0; 210 | mPrevTop = 0; 211 | mUpdated = false; 212 | } 213 | } 214 | 215 | static class PublicListener implements OnScrollListener { 216 | private Delegate mDelegate; 217 | 218 | public PublicListener(Delegate delegate) { 219 | mDelegate = delegate; 220 | } 221 | 222 | @Override 223 | public void onScrollStateChanged(AbsListView view, int scrollState) { 224 | if (mDelegate != null) { 225 | mDelegate.onScrollStateChanged(view, scrollState); 226 | } 227 | } 228 | 229 | @Override 230 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 231 | if (mDelegate != null) { 232 | mDelegate.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); 233 | } 234 | } 235 | 236 | public void destroy() { 237 | mDelegate = null; 238 | } 239 | } 240 | 241 | public static class Builder { 242 | private Activity mActivity; 243 | private int mTargetParentId = android.R.id.content; 244 | private ViewGroup mParent; 245 | private AbsListView mAbsListView; 246 | private int mIconColor = 0xff139eff; 247 | private TimeInterpolator mInterpolator; 248 | private long mDuration = 200; 249 | private OnClickListener mClickListener; 250 | private Drawable mIcon; 251 | 252 | private Builder(@NonNull Activity activity) { 253 | checkNotNull(activity, "Invalid Activity provided."); 254 | mActivity = activity; 255 | } 256 | 257 | public Builder in(@IdRes int targetParentId) { 258 | checkResId(targetParentId, "Invalid parent id."); 259 | mTargetParentId = targetParentId; 260 | return this; 261 | } 262 | 263 | public Builder in(ViewGroup parent) { 264 | checkNotNull(parent, "Invalid parent provided."); 265 | mParent = parent; 266 | return this; 267 | } 268 | 269 | public Builder listenTo(@IdRes int scrollableId) { 270 | checkResId(scrollableId, "Invalid view id."); 271 | final View view = mActivity.findViewById(scrollableId); 272 | if (!(view instanceof AbsListView)) { 273 | throw new IllegalArgumentException("Provided view can't be listened to."); 274 | } 275 | listenTo((AbsListView)view); 276 | return this; 277 | } 278 | 279 | public Builder listenTo(AbsListView absListView) { 280 | checkNotNull(absListView, "Invalid AbsListView provided."); 281 | mAbsListView = absListView; 282 | return this; 283 | } 284 | 285 | public Builder color(int color) { 286 | mIconColor = color; 287 | return this; 288 | } 289 | 290 | public Builder colorResId(@ColorRes int colorResId) { 291 | checkResId(colorResId, "Invalid color resource provided."); 292 | final int colorFromRes = mActivity.getResources().getColor(colorResId); 293 | return color(colorFromRes); 294 | } 295 | 296 | public Builder animInterpolator(TimeInterpolator interpolator) { 297 | mInterpolator = interpolator; 298 | return this; 299 | } 300 | 301 | public Builder animDuration(long duration) { 302 | if (duration < 0) { 303 | throw new IllegalArgumentException("Animation cannot have negative duration: " + duration); 304 | } 305 | mDuration = duration; 306 | return this; 307 | } 308 | 309 | public Builder icon(@DrawableRes int drawableResId) { 310 | checkResId(drawableResId, "Invalid icon resource provided."); 311 | final Drawable drawable = mActivity.getResources().getDrawable(drawableResId); 312 | return icon(drawable); 313 | } 314 | 315 | public Builder icon(Drawable drawable) { 316 | checkNotNull(drawable, "Invalid icon drawable provided."); 317 | mIcon = drawable; 318 | return this; 319 | } 320 | 321 | public Builder listener(OnClickListener listener) { 322 | checkNotNull(listener, "Invalid click listener provided."); 323 | mClickListener = listener; 324 | return this; 325 | } 326 | 327 | public FloatingAction build() { 328 | 329 | if (mInterpolator == null) { 330 | mInterpolator = new AccelerateDecelerateInterpolator(); 331 | } 332 | return new FloatingAction(this); 333 | } 334 | } 335 | 336 | static class Utils { 337 | static void checkNotNull(Object object, String msg) { 338 | if (object == null) { 339 | throw new NullPointerException(msg); 340 | } 341 | } 342 | 343 | static void checkResId(int resId, String msg) { 344 | if (resId < 0) { 345 | throw new IllegalArgumentException(msg); 346 | } 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /library/src/main/res/drawable-hdpi/fa_default_background_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-hdpi/fa_default_background_default.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-hdpi/fa_default_background_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-hdpi/fa_default_background_pressed.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-mdpi/fa_default_background_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-mdpi/fa_default_background_default.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-mdpi/fa_default_background_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-mdpi/fa_default_background_pressed.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/fa_default_background_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-xhdpi/fa_default_background_default.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/fa_default_background_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-xhdpi/fa_default_background_pressed.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxhdpi/fa_default_background_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-xxhdpi/fa_default_background_default.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxhdpi/fa_default_background_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-xxhdpi/fa_default_background_pressed.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/fa_default_background_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-xxxhdpi/fa_default_background_default.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/fa_default_background_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telly/FloatingAction/6b6b8caa2c50d509d822f6be04188d1452532945/library/src/main/res/drawable-xxxhdpi/fa_default_background_pressed.png -------------------------------------------------------------------------------- /library/src/main/res/drawable/fa_default_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/res/layout/fa_action_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':demo', ':library' --------------------------------------------------------------------------------