Initial commit

This commit is contained in:
Mariotaku Lee 2014-07-03 13:48:39 +08:00
commit 8b0b3f47b9
1378 changed files with 146052 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.gradle
/local.properties
/.idea/workspace.xml
.DS_Store
/build

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
Twidere

23
.idea/compiler.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Javac" />
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

5
.idea/encodings.xml Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>

18
.idea/gradle.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/twidere" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="acra-4.5.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.acra/acra/4.5.0/44a5eb946e0d83eed4a0128d877ca1fd8168b83a/acra-4.5.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/ch.acra/acra/4.5.0/503db72ba6ce28ef01be5e6ad0b73685bb8d6a27/acra-4.5.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="dashclock-api-2.0.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.android.apps.dashclock/dashclock-api/2.0.0/fe40ba5457e17502d11114f39b45e9dfe3f51c6d/dashclock-api-2.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.android.apps.dashclock/dashclock-api/2.0.0/ce76fed095cf56f18146bf2d598264518e05746/dashclock-api-2.0.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="dnsjava-ipv6-1.0-with-sources">
<CLASSES>
<root url="jar://$PROJECT_DIR$/twidere/libs/dnsjava-ipv6-1.0-with-sources.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="drag-sort-listview-apklib-1.0">
<CLASSES>
<root url="file://$USER_HOME$/.gradle/caches/modules-2/files-2.1/asia.ivity.android/drag-sort-listview-apklib/1.0/d6513067656508bf2a87bcb4427cfc91f7324552/drag-sort-listview-apklib-1.0.apklib" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="httpclient-android-4.3.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpclient-android/4.3.3/39ba75b570e5f1535356e8a1bf920b1c6cc83a9a/httpclient-android-4.3.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="httpmime-4.3.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpmime/4.3.3/e02368d7b56c0c376f227378a4f77de62864d020/httpmime-4.3.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpmime/4.3.3/884b613cfc42948ad5d4cf507286ffe19592ebed/httpmime-4.3.3-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="jsonserializer-1.0">
<CLASSES>
<root url="jar://$PROJECT_DIR$/twidere/libs/jsonserializer-1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="library-1.0">
<CLASSES>
<root url="file://$PROJECT_DIR$/build/intermediates/exploded-aar/com.negusoft.holoaccent/library/1.0/res" />
<root url="jar://$PROJECT_DIR$/build/intermediates/exploded-aar/com.negusoft.holoaccent/library/1.0/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="library-1.0.5">
<CLASSES>
<root url="jar://$PROJECT_DIR$/build/intermediates/exploded-aar/com.etsy.android.grid/library/1.0.5/classes.jar!/" />
<root url="file://$PROJECT_DIR$/build/intermediates/exploded-aar/com.etsy.android.grid/library/1.0.5/res" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="library-2.0.0">
<CLASSES>
<root url="jar://$PROJECT_DIR$/build/intermediates/exploded-aar/com.sothree.slidinguppanel/library/2.0.0/classes.jar!/" />
<root url="file://$PROJECT_DIR$/build/intermediates/exploded-aar/com.sothree.slidinguppanel/library/2.0.0/res" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="library-2.4.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.nineoldandroids/library/2.4.0/e9b63380f3a242dbdbf103a2355ad7e43bad17cb/library-2.4.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.nineoldandroids/library/2.4.0/83d41901478fa8795254244acbafd03f14224dab/library-2.4.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="library-2.4.1">
<CLASSES>
<root url="file://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.viewpagerindicator/library/2.4.1/b9ab2acb69cbfa1b33ba701aa59a1e277c0f63b7/library-2.4.1.apklib" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="merge-1.0.1">
<CLASSES>
<root url="jar://$PROJECT_DIR$/twidere/libs/merge-1.0.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="sacklist-1.0.0">
<CLASSES>
<root url="jar://$PROJECT_DIR$/twidere/libs/sacklist-1.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="snakeyaml-1.12">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/1.12/ebe66a6b88caab31d7a19571ad23656377523545/snakeyaml-1.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/1.12/f86c67beb22f7d1edb5d6c6a3c4dab77a23234da/snakeyaml-1.12-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="support-annotations-20.0.0">
<CLASSES>
<root url="jar://$APPLICATION_HOME_DIR$/sdk/extras/android/m2repository/com/android/support/support-annotations/20.0.0/support-annotations-20.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$APPLICATION_HOME_DIR$/sdk/extras/android/m2repository/com/android/support/support-annotations/20.0.0/support-annotations-20.0.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="support-v13-20.0.0">
<CLASSES>
<root url="file://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/support-v13/20.0.0/res" />
<root url="jar://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/support-v13/20.0.0/libs/internal_impl-20.0.0.jar!/" />
<root url="jar://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/support-v13/20.0.0/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="support-v4-20.0.0">
<CLASSES>
<root url="jar://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/20.0.0/classes.jar!/" />
<root url="jar://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/20.0.0/libs/internal_impl-20.0.0.jar!/" />
<root url="file://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/20.0.0/res" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="support-v4-r7">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.android/support-v4/r7/24d0f6da34c3a2bfcf736ab42d51c91ac821ee22/support-v4-r7.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.android/support-v4/r7/7ba1bec42f82297895e2a2631168a0c86e392e34/support-v4-r7-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="twitter-text-1.9.5">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.twitter/twitter-text/1.9.5/a0b48dac59206614b4190b9d23c72f27f16c9301/twitter-text-1.9.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.twitter/twitter-text/1.9.5/7a926174ffd9c3c60c3352541721a9a424706b4a/twitter-text-1.9.5-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="universal-image-loader-1.9.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.nostra13.universalimageloader/universal-image-loader/1.9.2/9e025bc5b84968e7d6ac765e660a70b3e5a87432/universal-image-loader-1.9.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.nostra13.universalimageloader/universal-image-loader/1.9.2/e52a7a5d1dbd9f817f314aa28a037d546a9241c2/universal-image-loader-1.9.2-sources.jar!/" />
</SOURCES>
</library>
</component>

10
.idea/misc.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
</project>

10
.idea/modules.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/Twidere-Android.iml" filepath="$PROJECT_DIR$/Twidere-Android.iml" />
<module fileurl="file://$PROJECT_DIR$/twidere/twidere.iml" filepath="$PROJECT_DIR$/twidere/twidere.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,5 @@
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>

7
.idea/vcs.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="" />
</component>
</project>

19
Twidere-Android.iml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

19
build.gradle Normal file
View File

@ -0,0 +1,19 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.+'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}

18
gradle.properties Normal file
View File

@ -0,0 +1,18 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Settings specified in this file will override any Gradle settings
# configured through the IDE.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip

164
gradlew vendored Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
settings.gradle Normal file
View File

@ -0,0 +1 @@
include ':twidere'

1
twidere/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

36
twidere/build.gradle Normal file
View File

@ -0,0 +1,36 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 19
buildToolsVersion "20.0.0"
defaultConfig {
applicationId "org.mariotaku.twidere"
minSdkVersion 14
targetSdkVersion 19
versionCode 95
versionName "0.2.9.14"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:support-v13:20.0.0'
compile 'com.google.android.gms:play-services:5.0.77'
compile 'com.negusoft.holoaccent:library:+'
compile 'com.etsy.android.grid:library:1.0.5'
compile 'com.viewpagerindicator:library:2.4.1'
compile 'com.sothree.slidinguppanel:library:2.0.0'
compile 'com.twitter:twitter-text:1.9.5'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.2'
compile 'org.apache.httpcomponents:httpclient-android:4.3.3'
compile 'org.apache.httpcomponents:httpmime:4.3.3'
compile 'ch.acra:acra:4.5.0'
compile 'com.google.android.apps.dashclock:dashclock-api:2.0.0'
compile fileTree(dir: 'libs', include: ['*.jar'])
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

17
twidere/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -0,0 +1,13 @@
package org.mariotaku.myapplication;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

Binary file not shown.

View File

@ -0,0 +1,694 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>GNU General Public License v3.0 - GNU Project - Free Software Foundation (FSF)</title>
<link rel="alternate" type="application/rdf+xml"
href="http://www.gnu.org/licenses/gpl-3.0.rdf" />
</head>
<body>
<h3 style="text-align: center;">GNU GENERAL PUBLIC LICENSE</h3>
<p style="text-align: center;">Version 3, 29 June 2007</p>
<p>Copyright &copy; 2007 Free Software Foundation, Inc.
&lt;<a href="http://fsf.org/">http://fsf.org/</a>&gt;</p><p>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.</p>
<h3><a name="preamble"></a>Preamble</h3>
<p>The GNU General Public License is a free, copyleft license for
software and other kinds of works.</p>
<p>The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.</p>
<p>When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.</p>
<p>To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.</p>
<p>For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.</p>
<p>Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.</p>
<p>For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.</p>
<p>Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.</p>
<p>Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.</p>
<p>The precise terms and conditions for copying, distribution and
modification follow.</p>
<h3><a name="terms"></a>TERMS AND CONDITIONS</h3>
<h4><a name="section0"></a>0. Definitions.</h4>
<p>&ldquo;This License&rdquo; refers to version 3 of the GNU General Public License.</p>
<p>&ldquo;Copyright&rdquo; also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.</p>
<p>&ldquo;The Program&rdquo; refers to any copyrightable work licensed under this
License. Each licensee is addressed as &ldquo;you&rdquo;. &ldquo;Licensees&rdquo; and
&ldquo;recipients&rdquo; may be individuals or organizations.</p>
<p>To &ldquo;modify&rdquo; a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a &ldquo;modified version&rdquo; of the
earlier work or a work &ldquo;based on&rdquo; the earlier work.</p>
<p>A &ldquo;covered work&rdquo; means either the unmodified Program or a work based
on the Program.</p>
<p>To &ldquo;propagate&rdquo; a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.</p>
<p>To &ldquo;convey&rdquo; a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.</p>
<p>An interactive user interface displays &ldquo;Appropriate Legal Notices&rdquo;
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.</p>
<h4><a name="section1"></a>1. Source Code.</h4>
<p>The &ldquo;source code&rdquo; for a work means the preferred form of the work
for making modifications to it. &ldquo;Object code&rdquo; means any non-source
form of a work.</p>
<p>A &ldquo;Standard Interface&rdquo; means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.</p>
<p>The &ldquo;System Libraries&rdquo; of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
&ldquo;Major Component&rdquo;, in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.</p>
<p>The &ldquo;Corresponding Source&rdquo; for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.</p>
<p>The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.</p>
<p>The Corresponding Source for a work in source code form is that
same work.</p>
<h4><a name="section2"></a>2. Basic Permissions.</h4>
<p>All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.</p>
<p>You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.</p>
<p>Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.</p>
<h4><a name="section3"></a>3. Protecting Users' Legal Rights From Anti-Circumvention Law.</h4>
<p>No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.</p>
<p>When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.</p>
<h4><a name="section4"></a>4. Conveying Verbatim Copies.</h4>
<p>You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.</p>
<p>You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.</p>
<h4><a name="section5"></a>5. Conveying Modified Source Versions.</h4>
<p>You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:</p>
<ul>
<li>a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.</li>
<li>b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
&ldquo;keep intact all notices&rdquo;.</li>
<li>c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.</li>
<li>d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.</li>
</ul>
<p>A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
&ldquo;aggregate&rdquo; if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.</p>
<h4><a name="section6"></a>6. Conveying Non-Source Forms.</h4>
<p>You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:</p>
<ul>
<li>a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.</li>
<li>b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.</li>
<li>c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.</li>
<li>d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.</li>
<li>e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.</li>
</ul>
<p>A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.</p>
<p>A &ldquo;User Product&rdquo; is either (1) a &ldquo;consumer product&rdquo;, which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, &ldquo;normally used&rdquo; refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.</p>
<p>&ldquo;Installation Information&rdquo; for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.</p>
<p>If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).</p>
<p>The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.</p>
<p>Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.</p>
<h4><a name="section7"></a>7. Additional Terms.</h4>
<p>&ldquo;Additional permissions&rdquo; are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.</p>
<p>When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.</p>
<p>Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:</p>
<ul>
<li>a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or</li>
<li>b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or</li>
<li>c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or</li>
<li>d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or</li>
<li>e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or</li>
<li>f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.</li>
</ul>
<p>All other non-permissive additional terms are considered &ldquo;further
restrictions&rdquo; within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.</p>
<p>If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.</p>
<p>Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.</p>
<h4><a name="section8"></a>8. Termination.</h4>
<p>You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).</p>
<p>However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.</p>
<p>Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.</p>
<p>Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.</p>
<h4><a name="section9"></a>9. Acceptance Not Required for Having Copies.</h4>
<p>You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.</p>
<h4><a name="section10"></a>10. Automatic Licensing of Downstream Recipients.</h4>
<p>Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.</p>
<p>An &ldquo;entity transaction&rdquo; is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.</p>
<p>You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.</p>
<h4><a name="section11"></a>11. Patents.</h4>
<p>A &ldquo;contributor&rdquo; is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's &ldquo;contributor version&rdquo;.</p>
<p>A contributor's &ldquo;essential patent claims&rdquo; are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, &ldquo;control&rdquo; includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.</p>
<p>Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.</p>
<p>In the following three paragraphs, a &ldquo;patent license&rdquo; is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To &ldquo;grant&rdquo; such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.</p>
<p>If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. &ldquo;Knowingly relying&rdquo; means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.</p>
<p>If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.</p>
<p>A patent license is &ldquo;discriminatory&rdquo; if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.</p>
<p>Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.</p>
<h4><a name="section12"></a>12. No Surrender of Others' Freedom.</h4>
<p>If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.</p>
<h4><a name="section13"></a>13. Use with the GNU Affero General Public License.</h4>
<p>Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.</p>
<h4><a name="section14"></a>14. Revised Versions of this License.</h4>
<p>The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.</p>
<p>Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License &ldquo;or any later version&rdquo; applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.</p>
<p>If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.</p>
<p>Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.</p>
<h4><a name="section15"></a>15. Disclaimer of Warranty.</h4>
<p>THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM &ldquo;AS IS&rdquo; WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.</p>
<h4><a name="section16"></a>16. Limitation of Liability.</h4>
<p>IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.</p>
<h4><a name="section17"></a>17. Interpretation of Sections 15 and 16.</h4>
<p>If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.</p>
<p>END OF TERMS AND CONDITIONS</p>
<h3><a name="howto"></a>How to Apply These Terms to Your New Programs</h3>
<p>If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.</p>
<p>To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the &ldquo;copyright&rdquo; line and a pointer to where the full notice is found.</p>
<pre> &lt;one line to give the program's name and a brief idea of what it does.&gt;
Copyright (C) &lt;year&gt; &lt;name of author&gt;
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.
</pre>
<p>Also add information on how to contact you by electronic and paper mail.</p>
<p>If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:</p>
<pre> &lt;program&gt; Copyright (C) &lt;year&gt; &lt;name of author&gt;
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
</pre>
<p>The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an &ldquo;about box&rdquo;.</p>
<p>You should also get your employer (if you work as a programmer) or school,
if any, to sign a &ldquo;copyright disclaimer&rdquo; for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
&lt;<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>&gt;.</p>
<p>The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
&lt;<a href="http://www.gnu.org/philosophy/why-not-lgpl.html">http://www.gnu.org/philosophy/why-not-lgpl.html</a>&gt;.</p>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<title>Google Maps</title>
<meta charset="UTF-8">
<meta name="viewport" user-scalable="no"/>
<style type="text/css">
html, body, #map_canvas {
background-image: url('images/loading_tile.png');
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=true"></script>
<script type="text/javascript">
var map;
function initialize() {
var latitude = 0;
var longitude = 0;
if (window.android) {
latitude = window.android.getLatitude();
longitude = window.android.getLongitude();
}
setupMap(latitude, longitude, 12);
setCenter(latitude, longitude);
setMark(latitude, longitude);
}
function setupMap(latitude, longitude, default_zoom) {
var options = {
zoom: default_zoom,
center: getLatLng(latitude, longitude),
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map_canvas'), options);
}
function getLatLng(latitude, longitude) {
return new google.maps.LatLng(latitude, longitude);
}
function setCenter(latitude, longitude) {
map.panTo(getLatLng(latitude, longitude));
}
function center() {
var latitude = 0;
var longitude = 0;
if (window.android) {
latitude = window.android.getLatitude();
longitude = window.android.getLongitude();
}
setCenter(latitude, longitude);
}
function setMark(latitude, longitude) {
var latlng = getLatLng(latitude, longitude);
var marker = new google.maps.Marker({
position: latlng,
map: map
});
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map_canvas"></div>
</body>
</html>

View File

@ -0,0 +1,713 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mariotaku.twidere"
android:installLocation="auto">
<uses-sdk />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.location"
android:required="false" />
<uses-feature
android:name="android.hardware.location.gps"
android:required="false" />
<uses-feature
android:name="android.hardware.location.network"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="org.mariotaku.twidere.permission.SHORTEN_STATUS" />
<uses-permission android:name="org.mariotaku.twidere.permission.UPLOAD_MEDIA" />
<uses-permission android:name="org.mariotaku.twidere.permission.SYNC_TIMELINE" />
<permission-group
android:name="org.mariotaku.twidere.permission.PERMISSION_GROUP"
android:label="@string/app_name" />
<permission
android:name="org.mariotaku.twidere.permission.SHORTEN_STATUS"
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP" />
<permission
android:name="org.mariotaku.twidere.permission.UPLOAD_MEDIA"
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP" />
<permission
android:name="org.mariotaku.twidere.permission.SYNC_TIMELINE"
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP" />
<application
android:name=".app.TwidereApplication"
android:allowBackup="true"
android:backupAgent="org.mariotaku.twidere.backup.TwidereBackupAgentHelper"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Blank">
<uses-library
android:name="com.sec.android.app.multiwindow"
android:required="false" />
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIKbKATV1AGbLB4kem3w8QaPVJSPVVumbMHxkfwA" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyCVdCIMFFxdNqHnCPrJ9yKUzoTfs8jhYGc" />
<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true" />
<meta-data
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W"
android:value="480dp" />
<meta-data
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H"
android:value="640dp" />
<meta-data
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W"
android:value="240dp" />
<meta-data
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H"
android:value="320dp" />
<meta-data
android:name="override_tinted_status_bar_defaults"
android:value="true" />
<activity
android:name=".activity.MainActivity"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/Theme.Launcher"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.MainHondaJOJOActivity"
android:enabled="false"
android:icon="@drawable/ic_launcher_hondajojo"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/Theme.Launcher"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.HomeActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<meta-data
android:name="android.app.default_searchable"
android:value=".activity.support.HomeActivity" />
</activity>
<activity
android:name=".activity.support.ComposeActivity"
android:excludeFromRecents="true"
android:label="@string/compose"
android:launchMode="singleTop"
android:theme="@style/Theme.Blank.Dialog"
android:windowSoftInputMode="adjustResize">
<intent-filter android:label="@string/compose">
<action android:name="android.intent.action.MAIN" />
<action android:name="org.mariotaku.twidere.COMPOSE" />
<action android:name="org.mariotaku.twidere.REPLY" />
<action android:name="org.mariotaku.twidere.QUOTE" />
<action android:name="org.mariotaku.twidere.EDIT_DRAFT" />
<action android:name="org.mariotaku.twidere.MENTION" />
<action android:name="org.mariotaku.twidere.REPLY_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:label="@string/share_via_twidere">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="text/plain" />
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.support.HomeActivity" />
</activity>
<activity
android:name=".activity.support.SignInActivity"
android:label="@string/sign_in"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.TWITTER_LOGIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.BrowserSignInActivity"
android:label="@string/browser_sign_in"
android:theme="@style/Theme.Blank.Dialog"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.TWITTER_BROWSER_LOGIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.SettingsActivity"
android:label="@string/settings"
android:theme="@style/Theme.Blank"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<action android:name="org.mariotaku.twidere.SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.support.HomeActivity" />
</activity>
<activity
android:name=".activity.FiltersActivity"
android:label="@string/filters">
<intent-filter>
<action android:name="org.mariotaku.twidere.FILTERS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.support.HomeActivity" />
</activity>
<activity
android:name=".activity.support.APIEditorActivity"
android:label="@string/edit_api"
android:theme="@style/Theme.Blank.Dialog"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".activity.support.AccountSelectorActivity"
android:label="@string/select_account"
android:theme="@style/Theme.Blank.Dialog">
<intent-filter>
<action android:name="org.mariotaku.twidere.SELECT_ACCOUNT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.BrowserActivity"
android:exported="false"
android:label="@string/browser">
<intent-filter>
<action android:name="org.mariotaku.twidere.VIEW_WEBPAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.ColorPickerDialogActivity"
android:label="@string/set_color"
android:theme="@style/Theme.Twidere.Light.NoDisplay" />
<activity
android:name=".activity.support.LinkHandlerActivity"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.support.HomeActivity" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="user"
android:scheme="twidere" />
<data
android:host="users"
android:scheme="twidere" />
<data
android:host="user_timeline"
android:scheme="twidere" />
<data
android:host="user_favorites"
android:scheme="twidere" />
<data
android:host="user_followers"
android:scheme="twidere" />
<data
android:host="user_friends"
android:scheme="twidere" />
<data
android:host="user_blocks"
android:scheme="twidere" />
<data
android:host="status"
android:scheme="twidere" />
<data
android:host="status_retweeters"
android:scheme="twidere" />
<data
android:host="status_favoriters"
android:scheme="twidere" />
<data
android:host="status_replies"
android:scheme="twidere" />
<data
android:host="statuses"
android:scheme="twidere" />
<data
android:host="direct_messages_conversation"
android:scheme="twidere" />
<data
android:host="user_list"
android:scheme="twidere" />
<data
android:host="user_lists"
android:scheme="twidere" />
<data
android:host="user_list_timeline"
android:scheme="twidere" />
<data
android:host="user_list_members"
android:scheme="twidere" />
<data
android:host="user_list_subscribers"
android:scheme="twidere" />
<data
android:host="user_list_memberships"
android:scheme="twidere" />
<data
android:host="saved_searches"
android:scheme="twidere" />
<data
android:host="user_mentions"
android:scheme="twidere" />
<data
android:host="incoming_friendships"
android:scheme="twidere" />
<data
android:host="search"
android:scheme="twidere" />
<data
android:host="mutes_users"
android:scheme="twidere" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.DraftsActivity"
android:exported="false"
android:label="@string/drafts"
android:launchMode="singleTop">
<intent-filter>
<action android:name="org.mariotaku.twidere.DRAFTS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.support.HomeActivity" />
</activity>
<activity
android:name="org.mariotaku.gallery3d.ImageViewerGLActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:hardwareAccelerated="true"
android:label="@string/view_image"
android:launchMode="singleTop"
android:process=":image_viewer"
android:theme="@style/Theme.Blank">
<intent-filter>
<action android:name="org.mariotaku.twidere.VIEW_IMAGE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="file" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.MapViewerActivity"
android:exported="false"
android:label="@string/view_map"
android:theme="@style/Theme.Blank">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="map"
android:scheme="twidere" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.FileSelectorActivity"
android:exported="false"
android:label="@string/pick_file"
android:theme="@style/Theme.Twidere.Light.NoDisplay"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.PICK_FILE" />
<action android:name="org.mariotaku.twidere.PICK_DIRECTORY" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.ImagePickerActivity"
android:exported="false"
android:theme="@style/Theme.Twidere.Light.NoDisplay"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="org.mariotaku.twidere.PICK_IMAGE" />
<action android:name="org.mariotaku.twidere.TAKE_PHOTO" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.UserProfileEditorActivity"
android:exported="false"
android:label="@string/edit_profile"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.EDIT_USER_PROFILE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.CustomTabsActivity"
android:label="@string/tabs"
android:theme="@style/Theme.Blank" />
<activity
android:name=".activity.support.CustomTabEditorActivity"
android:exported="false"
android:label="@string/add_tab"
android:theme="@style/Theme.Blank.Dialog"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.ADD_TAB" />
<action android:name="org.mariotaku.twidere.EDIT_TAB" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.CreateComposeShortcutActivity"
android:label="@string/compose"
android:theme="@style/Theme.Twidere.Dark.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.DataProfilingSettingsActivity"
android:exported="false"
android:label="@string/data_profiling"
android:theme="@style/Theme.Blank" />
<activity
android:name=".activity.support.RequestPermissionsActivity"
android:label="@string/permissions_request"
android:theme="@style/Theme.Blank.Dialog">
<intent-filter>
<action android:name="org.mariotaku.twidere.REQUEST_PERMISSIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.CameraCropActivity"
android:exported="false">
<intent-filter>
<action android:name="org.mariotaku.twidere.CAMERA_CROP" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.support.UserListSelectorActivity"
android:label="@string/select_user_list"
android:theme="@style/Theme.Blank.Dialog">
<intent-filter>
<action android:name="org.mariotaku.twidere.SELECT_USER" />
<action android:name="org.mariotaku.twidere.SELECT_USER_LIST" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.SettingsWizardActivity"
android:label="@string/settings_wizard"
android:theme="@style/Theme.Twidere.Wizard" />
<activity
android:name=".activity.support.DataExportActivity"
android:label="@string/export_settings"
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".activity.support.DataImportActivity"
android:label="@string/import_settings"
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".activity.support.ActivityPickerActivity"
android:theme="@style/Theme.Blank.Dialog">
<intent-filter>
<action android:name="org.mariotaku.twidere.PICK_ACTIVITY" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.TwitterLinkHandlerActivity"
android:excludeFromRecents="true"
android:taskAffinity=":twidere_twitter_link_handler"
android:theme="@style/Theme.Launcher">
<intent-filter>
<data
android:host="twitter.com"
android:scheme="http" />
<data
android:host="twitter.com"
android:scheme="https" />
<data
android:host="www.twitter.com"
android:scheme="http" />
<data
android:host="www.twitter.com"
android:scheme="https" />
<data
android:host="mobile.twitter.com"
android:scheme="http" />
<data
android:host="mobile.twitter.com"
android:scheme="https" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
<activity
android:name=".activity.AssistLauncherActivity"
android:excludeFromRecents="true"
android:taskAffinity=":twidere_assist_launcher"
android:theme="@style/Theme.Launcher">
<intent-filter>
<action android:name="android.intent.action.ASSIST" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="com.android.systemui.action_assist_icon"
android:resource="@drawable/ic_assist_twidere" />
</activity>
<activity
android:name=".activity.TestActivity"
android:launchMode="singleTop"
android:theme="@style/Theme.Twidere.Settings.Light"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".activity.NyanActivity"
android:launchMode="singleTop"
android:theme="@style/Theme.Nyan"
android:windowSoftInputMode="stateAlwaysHidden" />
<service
android:name=".service.RefreshService"
android:label="@string/label_refresh_service"
android:settingsActivity=".activity.SettingsActivity" />
<service
android:name=".service.BackgroundOperationService"
android:label="@string/label_background_operation_service" />
<service
android:name="edu.ucdavis.earlybird.UCDService"
android:settingsActivity=".activity.DataProfilingSettingsActivity" />
<service
android:name=".service.DashClockHomeUnreadCountService"
android:icon="@drawable/ic_extension_twidere"
android:label="@string/dashclock_home_unread_count_name"
android:permission="com.google.android.apps.dashclock.permission.READ_EXTENSION_DATA">
<intent-filter>
<action android:name="com.google.android.apps.dashclock.Extension" />
</intent-filter>
<meta-data
android:name="protocolVersion"
android:value="2" />
<meta-data
android:name="worldReadable"
android:value="true" />
<meta-data
android:name="description"
android:value="@string/dashclock_home_unread_count_description" />
</service>
<service
android:name=".service.DashClockMentionsUnreadCountService"
android:icon="@drawable/ic_extension_mentions"
android:label="@string/dashclock_mentions_unread_count_name"
android:permission="com.google.android.apps.dashclock.permission.READ_EXTENSION_DATA">
<intent-filter>
<action android:name="com.google.android.apps.dashclock.Extension" />
</intent-filter>
<meta-data
android:name="protocolVersion"
android:value="2" />
<meta-data
android:name="worldReadable"
android:value="true" />
<meta-data
android:name="description"
android:value="@string/dashclock_mentions_unread_count_description" />
</service>
<service
android:name=".service.DashClockMessagesUnreadCountService"
android:icon="@drawable/ic_extension_messages"
android:label="@string/dashclock_messages_unread_count_name"
android:permission="com.google.android.apps.dashclock.permission.READ_EXTENSION_DATA">
<intent-filter>
<action android:name="com.google.android.apps.dashclock.Extension" />
</intent-filter>
<meta-data
android:name="protocolVersion"
android:value="2" />
<meta-data
android:name="worldReadable"
android:value="true" />
<meta-data
android:name="description"
android:value="@string/dashclock_messages_unread_count_description" />
</service>
<service
android:name=".service.NyanWallpaperService"
android:enabled="false"
android:exported="true"
android:icon="@drawable/nyan_sakamoto_thumbnail"
android:label="@string/nyan_sakamoto"
android:permission="android.permission.BIND_WALLPAPER"
android:process=":wallpaper">
<intent-filter android:priority="1">
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/nyan_wallpaper" />
</service>
<service
android:name=".service.NyanDaydreamService"
android:enabled="false"
android:exported="true"
android:icon="@drawable/nyan_sakamoto_thumbnail"
android:label="@string/nyan_sakamoto"
android:process=":daydream">
<intent-filter android:priority="1">
<action android:name="android.service.dreams.DreamService" />
</intent-filter>
</service>
<provider
android:name=".provider.TwidereDataProvider"
android:authorities="twidere"
android:exported="true"
android:grantUriPermissions="true"
android:label="@string/label_tweetstore_provider" />
<provider
android:name=".provider.TwidereCommandProvider"
android:authorities="twidere.command"
android:exported="true" />
<provider
android:name=".provider.RecentSearchProvider"
android:authorities="org.mariotaku.twidere.provider.SearchRecentSuggestions" />
<receiver android:name=".receiver.ConnectivityStateReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
<receiver
android:name=".receiver.SecretCodeBroadcastReceiver"
android:label="@string/twidere_test">
<intent-filter>
<action android:name="android.provider.Telephony.SECRET_CODE" />
<data
android:host="8943373"
android:scheme="android_secret_code" />
</intent-filter>
</receiver>
<receiver
android:name="edu.ucdavis.earlybird.UploadReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
<action android:name="edu.ucdavis.earlybird.UPLOAD_PROFILE" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -0,0 +1,9 @@
package android.support.v4.app;
public class BackStackEntryTrojan {
public static Fragment getFragmentInBackStackRecord(final FragmentManager.BackStackEntry entry) {
if (entry instanceof BackStackRecord) return ((BackStackRecord) entry).mHead.fragment;
return null;
}
}

View File

@ -0,0 +1,9 @@
package android.support.v4.app;
public class FragmentManagerTrojan {
public static boolean isStateSaved(final FragmentManager fm) {
if (fm instanceof FragmentManagerImpl) return ((FragmentManagerImpl) fm).mStateSaved;
return false;
}
}

View File

@ -0,0 +1,11 @@
package android.support.v4.app;
import android.os.Bundle;
public class FragmentTrojan {
public static Bundle getSavedFragmentState(final Fragment f) {
return f.mSavedFragmentState;
}
}

View File

@ -0,0 +1,27 @@
/*
* Tweetings - Twitter client for Android
*
* Copyright (C) 2012 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package android.support.v4.app;
public interface ListFragmentTrojan {
public static final int INTERNAL_EMPTY_ID = ListFragment.INTERNAL_EMPTY_ID;
public static final int INTERNAL_PROGRESS_CONTAINER_ID = ListFragment.INTERNAL_PROGRESS_CONTAINER_ID;
public static final int INTERNAL_LIST_CONTAINER_ID = ListFragment.INTERNAL_LIST_CONTAINER_ID;
}

View File

@ -0,0 +1,274 @@
/*
* Copyright (C) 2013 Artur Termenji
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.atermenji.android.iconicdroid;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import com.atermenji.android.iconicdroid.icon.Icon;
/**
* A custom {@link Drawable} which can display icons from icon fonts.
*/
public class IconicFontDrawable extends Drawable {
private final Context mContext;
private final Paint mIconPaint;
private final Paint mContourPaint;
private final Rect mPaddingBounds;
private final RectF mPathBounds;
private final Path mPath;
private int mIconPadding;
private int mContourWidth;
private int mIntrinsicWidth;
private int mIntrinsicHeight;
private boolean mDrawContour;
private Icon mIcon;
private char[] mIconUtfChars;
public IconicFontDrawable(final Context context) {
mContext = context.getApplicationContext();
mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mContourPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mContourPaint.setStyle(Paint.Style.STROKE);
mPath = new Path();
mPathBounds = new RectF();
mPaddingBounds = new Rect();
}
public IconicFontDrawable(final Context context, final Icon icon) {
this(context);
updateIcon(icon);
}
@Override
public void draw(final Canvas canvas) {
if (mIcon != null) {
final Rect viewBounds = getBounds();
updatePaddingBounds(viewBounds);
updateTextSize(viewBounds);
offsetIcon(viewBounds);
mPath.close();
if (mDrawContour) {
canvas.drawPath(mPath, mContourPaint);
}
canvas.drawPath(mPath, mIconPaint);
}
}
/**
* Enable/disable contour drawing.
*
* @param drawContour
*/
public void drawContour(final boolean drawContour) {
mDrawContour = drawContour;
if (mDrawContour) {
mIconPadding += mContourWidth;
} else {
mIconPadding -= mContourWidth;
}
invalidateSelf();
}
@Override
public int getIntrinsicHeight() {
return mIntrinsicHeight;
}
@Override
public int getIntrinsicWidth() {
return mIntrinsicWidth;
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
@Override
public void setAlpha(final int alpha) {
mIconPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(final ColorFilter cf) {
mIconPaint.setColorFilter(cf);
}
/**
* Set contour params for the {@link Icon}. You should call
* {@link #drawContour(boolean)} method to enable contour.
*
* @param contourColor
* @param contourWidth
*/
public void setContour(final int contourColor, final int contourWidth) {
setContourColor(contourColor);
setContourWidth(contourWidth);
invalidateSelf();
}
/**
* Set contour color for the {@link Icon}. You should call
* {@link #drawContour(boolean)} method to enable contour.
*
* @param contourColor
*/
public void setContourColor(final int contourColor) {
mContourPaint.setColor(contourColor);
invalidateSelf();
}
/**
* Set contour width for the {@link Icon}. You should call
* {@link #drawContour(boolean)} method to enable contour.
*
* @param contourWidth
*/
public void setContourWidth(final int contourWidth) {
mContourWidth = contourWidth;
mContourPaint.setStrokeWidth(mContourWidth);
invalidateSelf();
}
/**
* Loads and draws given {@link Icon}.
*
* @param icon
*/
public void setIcon(final Icon icon) {
updateIcon(icon);
invalidateSelf();
}
/**
* Set a color for the {@link Icon}.
*
* @param color
*/
public void setIconColor(final int color) {
mIconPaint.setColor(color);
invalidateSelf();
}
/**
* Set a padding for the {@link Icon}.
*
* @param iconPadding
*/
public void setIconPadding(final int iconPadding) {
mIconPadding = iconPadding;
if (mDrawContour) {
mIconPadding += mContourWidth;
}
invalidateSelf();
}
/**
* Set intrinsic height, which is used by several controls.
*
* @param intrinsicHeight
*/
public void setIntrinsicHeight(final int intrinsicHeight) {
mIntrinsicHeight = intrinsicHeight;
}
/**
* Set intrinsic width, which is used by several controls.
*
* @param intrinsicWidth
*/
public void setIntrinsicWidth(final int intrinsicWidth) {
mIntrinsicWidth = intrinsicWidth;
}
private void offsetIcon(final Rect viewBounds) {
// final float startX = viewBounds.centerX() - mPathBounds.width() / 2;
// final float offsetX = startX - mPathBounds.left;
// final float startY = viewBounds.centerY() - mPathBounds.height() / 2;
// final float offsetY = startY - mPathBounds.top;
final FontMetrics metrics = mIconPaint.getFontMetrics();
final float textSize = mIconPaint.getTextSize();
final float offsetX = viewBounds.centerX() - textSize / 2;
final float offsetY = viewBounds.centerY() - (metrics.ascent + metrics.descent) / 2
+ (metrics.bottom - metrics.descent);
mPath.offset(offsetX, offsetY);
}
private void updateIcon(final Icon icon) {
mIcon = icon;
mIconUtfChars = Character.toChars(icon.getIconUtfValue());
mIconPaint.setTypeface(mIcon.getIconicTypeface().getTypeface(mContext));
}
private void updatePaddingBounds(final Rect viewBounds) {
if (mIconPadding >= 0 && !(mIconPadding * 2 > viewBounds.width()) && !(mIconPadding * 2 > viewBounds.height())) {
mPaddingBounds.set(viewBounds.left + mIconPadding, viewBounds.top + mIconPadding, viewBounds.right
- mIconPadding, viewBounds.bottom - mIconPadding);
}
}
private void updateTextSize(final Rect viewBounds) {
final float textSize = viewBounds.height();
mIconPaint.setTextSize(textSize);
mIconPaint.getTextPath(mIconUtfChars, 0, mIconUtfChars.length, 0, 0, mPath);
mPath.computeBounds(mPathBounds, true);
// final float deltaWidth = mPaddingBounds.width() /
// mPathBounds.width();
// final float deltaHeight = mPaddingBounds.height() /
// mPathBounds.height();
// final float delta = deltaWidth < deltaHeight ? deltaWidth :
// deltaHeight;
// textSize *= delta;
//
// mIconPaint.setTextSize(textSize);
// mIconPaint.getTextPath(mIconUtfChars, 0, mIconUtfChars.length, 0,
// viewBounds.height(), mPath);
// mPath.computeBounds(mPathBounds, true);
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2013 Artur Termenji
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.atermenji.android.iconicdroid.icon;
import android.graphics.Typeface;
import com.atermenji.android.iconicdroid.util.TypefaceManager.IconicTypeface;
import java.io.Serializable;
/**
* An interface which every icon font wrapper should implement.
*/
public interface Icon extends Serializable {
/**
* Gets a {@link Typeface} for an Icon.
*
* @return {@link IconicTypeface}
*/
public IconicTypeface getIconicTypeface();
/**
* Returns UTF value of an Icon.
*
* @return UTF value of an Icon
*/
public int getIconUtfValue();
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2013 Artur Termenji
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.atermenji.android.iconicdroid.util;
import android.content.Context;
import android.graphics.Typeface;
/**
* Helper class that wraps icon fonts and manages {@link Typeface} loading.
*/
public class TypefaceManager {
private TypefaceManager() {
}
public static interface IconicTypeface {
/**
* Loads a {@link Typeface} for the given icon font. {@link Typeface} is
* loaded only once to avoid memory consumption.
*
* @param context
* @return {@link Typeface}
*/
public Typeface getTypeface(final Context context);
}
}

View File

@ -0,0 +1,491 @@
package com.readystatesoftware.viewbadger;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.TabWidget;
import android.widget.TextView;
import org.mariotaku.twidere.util.accessor.ViewAccessor;
/**
* A simple text label view that can be applied as a "badge" to any given
* {@link android.view.View}. This class is intended to be instantiated at
* runtime rather than included in XML layouts.
*
* @author Jeff Gilfelt
*/
public class BadgeView extends TextView {
public static final int POSITION_TOP_LEFT = 1;
public static final int POSITION_TOP_RIGHT = 2;
public static final int POSITION_BOTTOM_LEFT = 3;
public static final int POSITION_BOTTOM_RIGHT = 4;
public static final int POSITION_CENTER = 5;
private static final int DEFAULT_MARGIN_DIP = 5;
private static final int DEFAULT_LR_PADDING_DIP = 5;
private static final int DEFAULT_CORNER_RADIUS_DIP = 8;
private static final int DEFAULT_POSITION = POSITION_TOP_RIGHT;
private static final int DEFAULT_BADGE_COLOR = Color.parseColor("#CCFF4444"); // holo_red_light;
private static final int DEFAULT_TEXT_COLOR = Color.WHITE;
private static Animation fadeIn;
private static Animation fadeOut;
private Context context;
private View target;
private int badgePosition;
private int badgeMarginH;
private int badgeMarginV;
private int badgeColor;
private boolean isShown;
private ShapeDrawable badgeBg;
private int targetTabIndex;
public BadgeView(final Context context) {
this(context, (AttributeSet) null, android.R.attr.textViewStyle);
}
public BadgeView(final Context context, final AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public BadgeView(final Context context, final AttributeSet attrs, final int defStyle) {
this(context, attrs, defStyle, null, 0);
}
public BadgeView(final Context context, final AttributeSet attrs, final int defStyle, final View target,
final int tabIndex) {
super(context, attrs, defStyle);
init(context, target, tabIndex);
}
/**
* Constructor -
*
* create a new BadgeView instance attached to a target
* {@link android.widget.TabWidget} tab at a given index.
*
* @param context context for this view.
* @param target the TabWidget to attach the badge to.
* @param index the position of the tab within the target.
*/
public BadgeView(final Context context, final TabWidget target, final int index) {
this(context, null, android.R.attr.textViewStyle, target, index);
}
/**
* Constructor -
*
* create a new BadgeView instance attached to a target
* {@link android.view.View}.
*
* @param context context for this view.
* @param target the View to attach the badge to.
*/
public BadgeView(final Context context, final View target) {
this(context, null, android.R.attr.textViewStyle, target, 0);
}
/**
* Decrement the numeric badge label. If the current badge label cannot be
* converted to an integer value, its label will be set to "0".
*
* @param offset the decrement offset.
*/
public int decrement(final int offset) {
return increment(-offset);
}
/**
* Returns the color value of the badge background.
*
*/
public int getBadgeBackgroundColor() {
return badgeColor;
}
/**
* Returns the positioning of this badge.
*
* one of POSITION_TOP_LEFT, POSITION_TOP_RIGHT, POSITION_BOTTOM_LEFT,
* POSITION_BOTTOM_RIGHT, POSTION_CENTER.
*
*/
public int getBadgePosition() {
return badgePosition;
}
/**
* Returns the horizontal margin from the target View that is applied to
* this badge.
*
*/
public int getHorizontalBadgeMargin() {
return badgeMarginH;
}
/**
* Returns the target View this badge has been attached to.
*
*/
public View getTarget() {
return target;
}
/**
* Returns the vertical margin from the target View that is applied to this
* badge.
*
*/
public int getVerticalBadgeMargin() {
return badgeMarginV;
}
/**
* Make the badge non-visible in the UI.
*
*/
public void hide() {
hide(false, null);
}
/**
* Make the badge non-visible in the UI.
*
* @param anim Animation to apply to the view when made non-visible.
*/
public void hide(final Animation anim) {
hide(true, anim);
}
/**
* Make the badge non-visible in the UI.
*
* @param animate flag to apply the default fade-out animation.
*/
public void hide(final boolean animate) {
hide(animate, fadeOut);
}
/**
* Increment the numeric badge label. If the current badge label cannot be
* converted to an integer value, its label will be set to "0".
*
* @param offset the increment offset.
*/
public int increment(final int offset) {
final CharSequence txt = getText();
int i;
if (txt != null) {
try {
i = Integer.parseInt(txt.toString());
} catch (final NumberFormatException e) {
i = 0;
}
} else {
i = 0;
}
i = i + offset;
setText(String.valueOf(i));
return i;
}
/**
* Is this badge currently visible in the UI?
*
*/
@Override
public boolean isShown() {
return isShown;
}
/**
* Set the color value of the badge background.
*
* @param badgeColor the badge background color.
*/
public void setBadgeBackgroundColor(final int badgeColor) {
this.badgeColor = badgeColor;
badgeBg = getDefaultBackground();
}
/**
* Set the horizontal/vertical margin from the target View that is applied
* to this badge.
*
* @param badgeMargin the margin in pixels.
*/
public void setBadgeMargin(final int badgeMargin) {
badgeMarginH = badgeMargin;
badgeMarginV = badgeMargin;
}
/**
* Set the horizontal/vertical margin from the target View that is applied
* to this badge.
*
* @param horizontal margin in pixels.
* @param vertical margin in pixels.
*/
public void setBadgeMargin(final int horizontal, final int vertical) {
badgeMarginH = horizontal;
badgeMarginV = vertical;
}
/**
* Set the positioning of this badge.
*
* @param layoutPosition one of POSITION_TOP_LEFT, POSITION_TOP_RIGHT,
* POSITION_BOTTOM_LEFT, POSITION_BOTTOM_RIGHT, POSTION_CENTER.
*
*/
public void setBadgePosition(final int layoutPosition) {
badgePosition = layoutPosition;
}
public int setCount(final int count) {
setText(String.valueOf(count));
return count;
}
/**
* Make the badge visible in the UI.
*
*/
public void show() {
show(false, null);
}
/**
* Make the badge visible in the UI.
*
* @param anim Animation to apply to the view when made visible.
*/
public void show(final Animation anim) {
show(true, anim);
}
/**
* Make the badge visible in the UI.
*
* @param animate flag to apply the default fade-in animation.
*/
public void show(final boolean animate) {
show(animate, fadeIn);
}
/**
* Toggle the badge visibility in the UI.
*
*/
public void toggle() {
toggle(false, null, null);
}
/**
* Toggle the badge visibility in the UI.
*
* @param animIn Animation to apply to the view when made visible.
* @param animOut Animation to apply to the view when made non-visible.
*/
public void toggle(final Animation animIn, final Animation animOut) {
toggle(true, animIn, animOut);
}
/**
* Toggle the badge visibility in the UI.
*
* @param animate flag to apply the default fade-in/out animation.
*/
public void toggle(final boolean animate) {
toggle(animate, fadeIn, fadeOut);
}
@Override
protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
setMinWidth(h);
super.onSizeChanged(w, h, oldw, oldh);
}
private void applyLayoutParams() {
final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
switch (badgePosition) {
case POSITION_TOP_LEFT:
lp.gravity = Gravity.LEFT | Gravity.TOP;
lp.setMargins(badgeMarginH, badgeMarginV, 0, 0);
break;
case POSITION_TOP_RIGHT:
lp.gravity = Gravity.RIGHT | Gravity.TOP;
lp.setMargins(0, badgeMarginV, badgeMarginH, 0);
break;
case POSITION_BOTTOM_LEFT:
lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
lp.setMargins(badgeMarginH, 0, 0, badgeMarginV);
break;
case POSITION_BOTTOM_RIGHT:
lp.gravity = Gravity.RIGHT | Gravity.BOTTOM;
lp.setMargins(0, 0, badgeMarginH, badgeMarginV);
break;
case POSITION_CENTER:
lp.gravity = Gravity.CENTER;
lp.setMargins(0, 0, 0, 0);
break;
default:
break;
}
setLayoutParams(lp);
}
private void applyTo(View target) {
final LayoutParams lp = target.getLayoutParams();
final ViewParent parent = target.getParent();
final FrameLayout container = new FrameLayout(context);
if (target instanceof TabWidget) {
// set target to the relevant tab child container
target = ((TabWidget) target).getChildTabViewAt(targetTabIndex);
this.target = target;
((ViewGroup) target).addView(container, new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
setVisibility(View.GONE);
container.addView(this);
} else {
// TODO verify that parent is indeed a ViewGroup
final ViewGroup group = (ViewGroup) parent;
final int index = group.indexOfChild(target);
group.removeView(target);
group.addView(container, index, lp);
container.addView(target);
setVisibility(View.GONE);
container.addView(this);
group.invalidate();
}
}
private int dipToPixels(final int dip) {
final Resources r = getResources();
final float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, r.getDisplayMetrics());
return (int) px;
}
private ShapeDrawable getDefaultBackground() {
final int r = dipToPixels(DEFAULT_CORNER_RADIUS_DIP);
final float[] outerR = new float[] { r, r, r, r, r, r, r, r };
final RoundRectShape rr = new RoundRectShape(outerR, null, null);
final ShapeDrawable drawable = new ShapeDrawable(rr);
drawable.getPaint().setColor(badgeColor);
return drawable;
}
private void hide(final boolean animate, final Animation anim) {
setVisibility(View.GONE);
if (animate) {
startAnimation(anim);
}
isShown = false;
}
private void init(final Context context, final View target, final int tabIndex) {
this.context = context;
this.target = target;
targetTabIndex = tabIndex;
setGravity(Gravity.CENTER);
// apply defaults
badgePosition = DEFAULT_POSITION;
badgeMarginH = dipToPixels(DEFAULT_MARGIN_DIP);
badgeMarginV = badgeMarginH;
badgeColor = DEFAULT_BADGE_COLOR;
setTypeface(Typeface.DEFAULT_BOLD);
final int paddingPixels = dipToPixels(DEFAULT_LR_PADDING_DIP);
setPadding(paddingPixels, 0, paddingPixels, 0);
setTextColor(DEFAULT_TEXT_COLOR);
fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new DecelerateInterpolator());
fadeIn.setDuration(200);
fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator());
fadeOut.setDuration(200);
isShown = false;
if (this.target != null) {
applyTo(this.target);
} else {
show();
}
}
private void show(final boolean animate, final Animation anim) {
if (getBackground() == null) {
if (badgeBg == null) {
badgeBg = getDefaultBackground();
}
ViewAccessor.setBackground(this, badgeBg);
}
applyLayoutParams();
if (animate) {
startAnimation(anim);
}
setVisibility(View.VISIBLE);
isShown = true;
}
private void toggle(final boolean animate, final Animation animIn, final Animation animOut) {
if (isShown) {
hide(animate && animOut != null, animOut);
} else {
show(animate && animIn != null, animIn);
}
}
}

View File

@ -0,0 +1,517 @@
package com.scvngr.levelup.views.gallery;
/*
* Modified from the Android Source code. The example for how to do so was viewed here:
* http://www.inter-fuser.com/2010/01/android-coverflow-widget.html
*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.SpinnerAdapter;
/**
* An abstract base class for spinner widgets. SDK users will probably not need
* to use this class.
*
* CHECKSTYLE:OFF Android Attr
*
* @attr ref android.R.styleable#AbsSpinner_entries CHECKSTYLE:ON
*/
public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
SpinnerAdapter mAdapter;
int mHeightMeasureSpec;
int mWidthMeasureSpec;
boolean mBlockLayoutRequests;
int mSelectionLeftPadding = 0;
int mSelectionTopPadding = 0;
int mSelectionRightPadding = 0;
int mSelectionBottomPadding = 0;
Rect mSpinnerPadding = new Rect();
View mSelectedView = null;
Interpolator mInterpolator;
RecycleBin mRecycler = new RecycleBin();
private DataSetObserver mDataSetObserver;
/*
* Temporary frame to hold a child View's frame rectangle.
*/
private Rect mTouchFrame;
/**
* Constructor.
*
* @param context the context to inflate into
*/
public AbsSpinner(final Context context) {
super(context);
initAbsSpinner();
}
/**
* Constructor for xml inflation.
*
* @param context the context to inflate into
* @param attrs the xml attrs
*/
public AbsSpinner(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Constructor for xml inflation.
*
* @param context the context to inflate into
* @param attrs the xml attrs
* @param defStyle the default style resource
*/
public AbsSpinner(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
initAbsSpinner();
}
@Override
public final SpinnerAdapter getAdapter() {
return mAdapter;
}
@Override
public final int getCount() {
return mItemCount;
}
@Override
public final View getSelectedView() {
if (mItemCount > 0 && mSelectedPosition >= 0)
return getChildAt(mSelectedPosition - mFirstPosition);
else
return null;
}
@Override
public void onRestoreInstanceState(final Parcelable state) {
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.selectedId >= 0) {
mDataChanged = true;
mNeedSync = true;
mSyncRowId = ss.selectedId;
mSyncPosition = ss.position;
mSyncMode = SYNC_SELECTED_POSITION;
requestLayout();
}
}
@Override
public Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
final SavedState ss = new SavedState(superState);
ss.selectedId = getSelectedItemId();
if (ss.selectedId >= 0) {
ss.position = getSelectedItemPosition();
} else {
ss.position = INVALID_POSITION;
}
return ss;
}
/**
* Maps a point to a position in the list.
*
* @param x X in local coordinate
* @param y Y in local coordinate
* @return The position of the item which contains the specified point, or
* {@link #INVALID_POSITION} if the point does not intersect an
* item.
*/
public final int pointToPosition(final int x, final int y) {
Rect frame = mTouchFrame;
if (frame == null) {
mTouchFrame = new Rect();
frame = mTouchFrame;
}
final int count = getChildCount();
for (int i = count - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getVisibility() == View.VISIBLE) {
child.getHitRect(frame);
if (frame.contains(x, y)) return mFirstPosition + i;
}
}
return INVALID_POSITION;
}
/**
* Override to prevent spamming ourselves with layout requests as we place
* views.
*
* @see android.view.View#requestLayout()
*/
@Override
public final void requestLayout() {
if (!mBlockLayoutRequests) {
super.requestLayout();
}
}
/**
* The Adapter is used to provide the data which backs this Spinner. It also
* provides methods to transform spinner items based on their position
* relative to the selected item.
*
* @param adapter The SpinnerAdapter to use for this Spinner
*/
// CHECKSTYLE:OFF unmodified
@Override
public void setAdapter(final SpinnerAdapter adapter) {
if (null != mAdapter) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
resetList();
}
mAdapter = adapter;
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
if (mAdapter != null) {
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
final int position = mItemCount > 0 ? 0 : INVALID_POSITION;
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
checkFocus();
resetList();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}
// CHECKSTYLE:ON
@Override
public final void setSelection(final int position) {
setNextSelectedPositionInt(position);
requestLayout();
invalidate();
}
/**
* Jump directly to a specific item in the adapter data.
*
* @param position the position to select
* @param animate if true, animate the selection
*/
public final void setSelection(final int position, final boolean animate) {
// Animate only if requested position is already on screen somewhere
final boolean shouldAnimate = animate && mFirstPosition <= position
&& position <= mFirstPosition + getChildCount() - 1;
setSelectionInt(position, shouldAnimate);
}
// CHECKSTYLE:OFF overridden in gallery
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
// CHECKSTYLE:ON
return new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
/**
* @see android.view.View#measure(int, int)
*
* Figure out the dimensions of this Spinner. The width comes from the
* widthMeasureSpec as Spinnners can't have their width set to
* UNSPECIFIED. The height is based on the height of the selected item
* plus padding.
*/
// CHECKSTYLE:OFF unmodified
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize;
int heightSize;
mSpinnerPadding.left = getPaddingLeft() > mSelectionLeftPadding ? getPaddingLeft() : mSelectionLeftPadding;
mSpinnerPadding.top = getPaddingTop() > mSelectionTopPadding ? getPaddingTop() : mSelectionTopPadding;
mSpinnerPadding.right = getPaddingRight() > mSelectionRightPadding ? getPaddingRight() : mSelectionRightPadding;
mSpinnerPadding.bottom = getPaddingBottom() > mSelectionBottomPadding ? getPaddingBottom()
: mSelectionBottomPadding;
if (mDataChanged) {
handleDataChanged();
}
int preferredHeight = 0;
int preferredWidth = 0;
boolean needsMeasuring = true;
final int selectedPosition = getSelectedItemPosition();
if (selectedPosition >= 0 && mAdapter != null) {
// Try looking in the recycler. (Maybe we were measured once
// already)
View view = mRecycler.get(selectedPosition);
if (view == null) {
// Make a new one
view = mAdapter.getView(selectedPosition, null, this);
}
if (view != null) {
// Put in recycler for re-measuring and/or layout
mRecycler.put(selectedPosition, view);
}
if (view != null) {
if (view.getLayoutParams() == null) {
mBlockLayoutRequests = true;
view.setLayoutParams(generateDefaultLayoutParams());
mBlockLayoutRequests = false;
}
measureChild(view, widthMeasureSpec, heightMeasureSpec);
preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
needsMeasuring = false;
}
}
if (needsMeasuring) {
// No views -- just use padding
preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
if (widthMode == MeasureSpec.UNSPECIFIED) {
preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
}
}
preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
heightSize = resolveSize(preferredHeight, heightMeasureSpec);
widthSize = resolveSize(preferredWidth, widthMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
mHeightMeasureSpec = heightMeasureSpec;
mWidthMeasureSpec = widthMeasureSpec;
}
// CHECKSTYLE:ON
/**
* Common code for different constructor flavors.
*/
private void initAbsSpinner() {
setFocusable(true);
setWillNotDraw(false);
}
/**
* Gets the height of the child view passed.
*
* @param child the child to get the height of
* @return the child's measured height
*/
final int getChildHeight(final View child) {
return child.getMeasuredHeight();
}
/**
* Gets the width of the child view passed.
*
* @param child the child to get the width of
* @return the child's measure width
*/
final int getChildWidth(final View child) {
return child.getMeasuredWidth();
}
@Override
final void handleDataChanged() {
/*
* FIXME -- this is called from both measure and layout. This is
* harmless right now, but we don't want to do redundant work if this
* gets more complicated
*/
super.handleDataChanged();
}
// CHECKSTYLE:OFF unmodified
abstract void layout(int delta, boolean animate);
// CHECKSTYLE:ON
/**
* Recycle all child views.
*/
final void recycleAllViews() {
final int childCount = getChildCount();
final AbsSpinner.RecycleBin recycleBin = mRecycler;
// All views go in recycler
for (int i = 0; i < childCount; i++) {
final View v = getChildAt(i);
final int index = mFirstPosition + i;
recycleBin.put(index, v);
}
}
/**
* Clear out all children from the list.
*/
final void resetList() {
mDataChanged = false;
mNeedSync = false;
removeAllViewsInLayout();
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
setSelectedPositionInt(INVALID_POSITION);
setNextSelectedPositionInt(INVALID_POSITION);
invalidate();
}
/**
* Makes the item at the supplied position selected.
*
* @param position Position to select
* @param animate Should the transition be animated
*
*/
final void setSelectionInt(final int position, final boolean animate) {
if (position != mOldSelectedPosition) {
mBlockLayoutRequests = true;
final int delta = position - mSelectedPosition;
setNextSelectedPositionInt(position);
layout(delta, animate);
mBlockLayoutRequests = false;
}
}
class RecycleBin {
private final SparseArray<View> mScrapHeap = new SparseArray<View>();
public void put(final int position, final View v) {
mScrapHeap.put(position, v);
}
void clear() {
final SparseArray<View> scrapHeap = mScrapHeap;
final int count = scrapHeap.size();
for (int i = 0; i < count; i++) {
final View view = scrapHeap.valueAt(i);
if (view != null) {
removeDetachedView(view, true);
}
}
scrapHeap.clear();
}
View get(final int position) {
// System.out.print("Looking for " + position);
final View result = mScrapHeap.get(position);
if (result != null) {
// System.out.println(" HIT");
mScrapHeap.delete(position);
} else {
// System.out.println(" MISS");
}
return result;
}
View peek(final int position) {
// System.out.print("Looking for " + position);
return mScrapHeap.get(position);
}
}
// CHECKSTYLE:ON
// CHECKSTYLE:OFF unmodified
static class SavedState extends BaseSavedState {
long selectedId;
int position;
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(final Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(final int size) {
return new SavedState[size];
}
};
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(final Parcel in) {
super(in);
selectedId = in.readLong();
position = in.readInt();
}
/**
* Constructor called from {@link AbsSpinner#onSaveInstanceState()}
*/
SavedState(final Parcelable superState) {
super(superState);
}
@Override
public String toString() {
return "AbsSpinner.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " selectedId="
+ selectedId + " position=" + position + "}";
}
@Override
public void writeToParcel(final Parcel out, final int flags) {
super.writeToParcel(out, flags);
out.writeLong(selectedId);
out.writeInt(position);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,847 @@
/*
* Copyright 2012 - 2013 Benjamin Weiss
* Copyright 2012 Neofonie Mobile GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.keyboardsurfer.android.widget.crouton;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.util.TypedValue;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.mariotaku.twidere.util.accessor.ViewAccessor;
/*
* Based on an article by Cyril Mottier (http://android.cyrilmottier.com/?p=773) <br>
*/
/**
* Displays information in a non-invasive context related manner. Like
* {@link android.widget.Toast}, but better.
* <p/>
* <b>Important: </b> Call {@link Crouton#clearCroutonsForActivity(Activity)}
* within {@link android.app.Activity#onDestroy()} to avoid {@link Context}
* leaks.
*/
public final class Crouton {
private static final int IMAGE_ID = 0x100;
private static final int TEXT_ID = 0x101;
private final CharSequence text;
private final CroutonStyle style;
private CroutonConfiguration configuration = null;
private final View customView;
private OnClickListener onClickListener;
private Activity activity;
private ViewGroup viewGroup;
private FrameLayout croutonView;
private Animation inAnimation;
private Animation outAnimation;
private CroutonLifecycleCallback lifecycleCallback = null;
/**
* Creates the {@link Crouton}.
*
* @param activity The {@link Activity} that the {@link Crouton} should be
* attached to.
* @param text The text you want to display.
* @param style The style that this {@link Crouton} should be created with.
*/
private Crouton(final Activity activity, final CharSequence text, final CroutonStyle style) {
if (activity == null || text == null || style == null)
throw new IllegalArgumentException("Null parameters are not accepted");
this.activity = activity;
viewGroup = null;
this.text = text;
this.style = style;
customView = null;
}
/**
* Creates the {@link Crouton}.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param text The text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroup The {@link ViewGroup} that this {@link Crouton} should
* be added to.
*/
private Crouton(final Activity activity, final CharSequence text, final CroutonStyle style,
final ViewGroup viewGroup) {
if (activity == null || text == null || style == null)
throw new IllegalArgumentException("Null parameters are not accepted");
this.activity = activity;
this.text = text;
this.style = style;
this.viewGroup = viewGroup;
customView = null;
}
/**
* Creates the {@link Crouton}.
*
* @param activity The {@link Activity} that the {@link Crouton} should be
* attached to.
* @param customView The custom {@link View} to display
*/
private Crouton(final Activity activity, final View customView) {
if (activity == null || customView == null)
throw new IllegalArgumentException("Null parameters are not accepted");
this.activity = activity;
viewGroup = null;
this.customView = customView;
style = new CroutonStyle.Builder().build();
text = null;
}
/**
* Creates the {@link Crouton}.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param customView The custom {@link View} to display
* @param viewGroup The {@link ViewGroup} that this {@link Crouton} should
* be added to.
*/
private Crouton(final Activity activity, final View customView, final ViewGroup viewGroup) {
this(activity, customView, viewGroup, CroutonConfiguration.DEFAULT);
}
/**
* Creates the {@link Crouton}.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param customView The custom {@link View} to display
* @param viewGroup The {@link ViewGroup} that this {@link Crouton} should
* be added to.
* @param configuration The {@link CroutonConfiguration} for this
* {@link Crouton}.
*/
private Crouton(final Activity activity, final View customView, final ViewGroup viewGroup,
final CroutonConfiguration configuration) {
if (activity == null || customView == null)
throw new IllegalArgumentException("Null parameters are not accepted");
this.activity = activity;
this.customView = customView;
this.viewGroup = viewGroup;
style = new CroutonStyle.Builder().build();
text = null;
this.configuration = configuration;
}
/** Cancels a {@link Crouton} immediately. */
public void cancel() {
final CroutonManager manager = CroutonManager.getInstance();
manager.removeCroutonImmediately(this);
}
public Animation getInAnimation() {
if (null == inAnimation && null != activity) {
if (getConfiguration().inAnimationResId > 0) {
inAnimation = AnimationUtils.loadAnimation(getActivity(), getConfiguration().inAnimationResId);
} else {
measureCroutonView();
inAnimation = DefaultAnimationsBuilder.buildDefaultSlideInDownAnimation(getView());
}
}
return inAnimation;
}
public Animation getOutAnimation() {
if (null == outAnimation && null != activity) {
if (getConfiguration().outAnimationResId > 0) {
outAnimation = AnimationUtils.loadAnimation(getActivity(), getConfiguration().outAnimationResId);
} else {
outAnimation = DefaultAnimationsBuilder.buildDefaultSlideOutUpAnimation(getView());
}
}
return outAnimation;
}
/**
* Set the {@link CroutonConfiguration} on this {@link Crouton}, prior to
* showing it.
*
* @param configuration a {@link CroutonConfiguration} built using the
* {@link CroutonConfiguration.Builder}.
* @return this {@link Crouton}.
*/
public Crouton setConfiguration(final CroutonConfiguration configuration) {
this.configuration = configuration;
return this;
}
/**
* @param lifecycleCallback Callback object for notable events in the life
* of a Crouton.
*/
public void setLifecycleCallback(final CroutonLifecycleCallback lifecycleCallback) {
this.lifecycleCallback = lifecycleCallback;
}
/**
* Allows setting of an {@link OnClickListener} directly to a
* {@link Crouton} without having to use a custom view.
*
* @param onClickListener The {@link OnClickListener} to set.
* @return this {@link Crouton}.
*/
public Crouton setOnClickListener(final OnClickListener onClickListener) {
this.onClickListener = onClickListener;
return this;
}
/**
* Displays the {@link Crouton}. If there's another {@link Crouton} visible
* at the time, this {@link Crouton} will be displayed afterwards.
*/
public void show() {
CroutonManager.getInstance().add(this);
}
@Override
public String toString() {
return "Crouton{" + "text=" + text + ", style=" + style + ", configuration=" + configuration + ", customView="
+ customView + ", onClickListener=" + onClickListener + ", activity=" + activity + ", viewGroup="
+ viewGroup + ", croutonView=" + croutonView + ", inAnimation=" + inAnimation + ", outAnimation="
+ outAnimation + ", lifecycleCallback=" + lifecycleCallback + '}';
}
private RelativeLayout initializeContentView(final Resources resources) {
final RelativeLayout contentView = new RelativeLayout(activity);
contentView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.WRAP_CONTENT));
// set padding
int padding = style.paddingInPixels;
// if a padding dimension has been set, this will overwrite any padding
// in pixels
if (style.paddingDimensionResId > 0) {
padding = resources.getDimensionPixelSize(style.paddingDimensionResId);
}
contentView.setPadding(padding, padding, padding, padding);
// only setup image if one is requested
ImageView image = null;
if (null != style.imageDrawable || 0 != style.imageResId) {
image = initializeImageView();
contentView.addView(image, image.getLayoutParams());
}
final TextView text = initializeTextView(resources);
final RelativeLayout.LayoutParams textParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
if (null != image) {
textParams.addRule(RelativeLayout.RIGHT_OF, image.getId());
}
contentView.addView(text, textParams);
return contentView;
}
private void initializeCroutonView() {
final Resources resources = activity.getResources();
croutonView = initializeCroutonViewGroup(resources);
// create content view
final RelativeLayout contentView = initializeContentView(resources);
croutonView.addView(contentView);
}
private FrameLayout initializeCroutonViewGroup(final Resources resources) {
final FrameLayout croutonView = new FrameLayout(activity);
if (null != onClickListener) {
croutonView.setOnClickListener(onClickListener);
}
final int height;
if (style.heightDimensionResId > 0) {
height = resources.getDimensionPixelSize(style.heightDimensionResId);
} else {
height = style.heightInPixels;
}
final int width;
if (style.widthDimensionResId > 0) {
width = resources.getDimensionPixelSize(style.widthDimensionResId);
} else {
width = style.widthInPixels;
}
croutonView.setLayoutParams(new FrameLayout.LayoutParams(width != 0 ? width
: FrameLayout.LayoutParams.MATCH_PARENT, height));
// set background
if (style.backgroundColorValue != -1) {
croutonView.setBackgroundColor(style.backgroundColorValue);
} else {
croutonView.setBackgroundColor(resources.getColor(style.backgroundColorResourceId));
}
// set the background drawable if set. This will override the background
// color.
if (style.backgroundDrawableResourceId != 0) {
final Bitmap background = BitmapFactory.decodeResource(resources, style.backgroundDrawableResourceId);
final BitmapDrawable drawable = new BitmapDrawable(resources, background);
if (style.isTileEnabled) {
drawable.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
}
ViewAccessor.setBackground(croutonView, drawable);
}
return croutonView;
}
private ImageView initializeImageView() {
ImageView image;
image = new ImageView(activity);
image.setId(IMAGE_ID);
image.setAdjustViewBounds(true);
image.setScaleType(style.imageScaleType);
// set the image drawable if not null
if (null != style.imageDrawable) {
image.setImageDrawable(style.imageDrawable);
}
// set the image resource if not 0. This will overwrite the drawable
// if both are set
if (style.imageResId != 0) {
image.setImageResource(style.imageResId);
}
final RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
imageParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
imageParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
image.setLayoutParams(imageParams);
return image;
}
private TextView initializeTextView(final Resources resources) {
final TextView text = new TextView(activity);
text.setId(TEXT_ID);
text.setText(this.text);
text.setContentDescription(this.text);
text.setTypeface(Typeface.DEFAULT_BOLD);
text.setGravity(style.gravity);
// set the text color if set
if (style.textColorResourceId != 0) {
text.setTextColor(resources.getColor(style.textColorResourceId));
}
// Set the text size. If the user has set a text size and text
// appearance, the text size in the text appearance
// will override this.
if (style.textSize != 0) {
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, style.textSize);
}
// Setup the shadow if requested
if (style.textShadowColorResId != 0) {
initializeTextViewShadow(resources, text);
}
// Set the text appearance
if (style.textAppearanceResId != 0) {
text.setTextAppearance(activity, style.textAppearanceResId);
}
return text;
}
private void initializeTextViewShadow(final Resources resources, final TextView text) {
final int textShadowColor = resources.getColor(style.textShadowColorResId);
final float textShadowRadius = style.textShadowRadius;
final float textShadowDx = style.textShadowDx;
final float textShadowDy = style.textShadowDy;
text.setShadowLayer(textShadowRadius, textShadowDx, textShadowDy, textShadowColor);
}
private boolean isCroutonViewNotNull() {
return null != croutonView && null != croutonView.getParent();
}
private boolean isCustomViewNotNull() {
return null != customView && null != customView.getParent();
}
private void measureCroutonView() {
final View view = getView();
int widthSpec;
if (viewGroup != null) {
widthSpec = View.MeasureSpec.makeMeasureSpec(viewGroup.getMeasuredWidth(), View.MeasureSpec.AT_MOST);
} else {
widthSpec = View.MeasureSpec.makeMeasureSpec(activity.getWindow().getDecorView().getMeasuredWidth(),
View.MeasureSpec.AT_MOST);
}
view.measure(widthSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
}
/** Removes the activity reference this {@link Crouton} is holding */
void detachActivity() {
activity = null;
}
/** Removes the lifecycleCallback reference this {@link Crouton} is holding */
void detachLifecycleCallback() {
lifecycleCallback = null;
}
/** Removes the viewGroup reference this {@link Crouton} is holding */
void detachViewGroup() {
viewGroup = null;
}
/** @return the activity */
Activity getActivity() {
return activity;
}
/** @return this croutons configuration */
CroutonConfiguration getConfiguration() {
if (null == configuration) {
configuration = getStyle().configuration;
}
return configuration;
}
/** @return the lifecycleCallback */
CroutonLifecycleCallback getLifecycleCallback() {
return lifecycleCallback;
}
/** @return the style */
CroutonStyle getStyle() {
return style;
}
/** @return the text */
CharSequence getText() {
return text;
}
/** @return the view */
View getView() {
// return the custom view if one exists
if (null != customView) return customView;
// if already setup return the view
if (null == croutonView) {
initializeCroutonView();
}
return croutonView;
}
/** @return the viewGroup */
ViewGroup getViewGroup() {
return viewGroup;
}
/**
* @return <code>true</code> if the {@link Crouton} is being displayed, else
* <code>false</code>.
*/
boolean isShowing() {
return null != activity && (isCroutonViewNotNull() || isCustomViewNotNull());
}
/**
* Cancels all queued {@link Crouton}s. If there is a {@link Crouton}
* displayed currently, it will be the last one displayed.
*/
public static void cancelAllCroutons() {
CroutonManager.getInstance().clearCroutonQueue();
}
/**
* Clears (and removes from {@link Activity}'s content view, if necessary)
* all croutons for the provided activity
*
* @param activity - The {@link Activity} to clear the croutons for.
*/
public static void clearCroutonsForActivity(final Activity activity) {
CroutonManager.getInstance().clearCroutonsForActivity(activity);
}
/**
* Convenience method to get the license text for embedding within your
* application.
*
* @return The license text.
*/
public static String getLicenseText() {
return "This application uses the Crouton library.\n\n" + "Copyright 2012 - 2013 Benjamin Weiss \n"
+ "Copyright 2012 Neofonie Mobile GmbH\n" + "\n"
+ "Licensed under the Apache License, Version 2.0 (the \"License\");\n"
+ "you may not use this file except in compliance with the License.\n"
+ "You may obtain a copy of the License at\n" + "\n"
+ " http://www.apache.org/licenses/LICENSE-2.0\n" + "\n"
+ "Unless required by applicable law or agreed to in writing, software\n"
+ "distributed under the License is distributed on an \"AS IS\" BASIS,\n"
+ "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
+ "See the License for the specific language governing permissions and\n"
+ "limitations under the License.";
}
/**
* Allows hiding of a previously displayed {@link Crouton}.
*
* @param crouton The {@link Crouton} you want to hide.
*/
public static void hide(final Crouton crouton) {
CroutonManager.getInstance().removeCrouton(crouton);
}
// ////////////////////////////////////////////////////////////////////////////////////
// You have reached the internal API of Crouton.
// If you do not plan to develop for Crouton there is nothing of interest
// below here.
// ////////////////////////////////////////////////////////////////////////////////////
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity.
*
* @param activity The {@link Activity} that the {@link Crouton} should be
* attached to.
* @param customView The custom {@link View} to display
* @return The created {@link Crouton}.
*/
public static Crouton make(final Activity activity, final View customView) {
return new Crouton(activity, customView);
}
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param customView The custom {@link View} to display
* @param viewGroupResId The resource id of the {@link ViewGroup} that this
* {@link Crouton} should be added to.
* @return The created {@link Crouton}.
*/
public static Crouton make(final Activity activity, final View customView, final int viewGroupResId) {
return new Crouton(activity, customView, (ViewGroup) activity.findViewById(viewGroupResId));
}
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param customView The custom {@link View} to display
* @param viewGroupResId The resource id of the {@link ViewGroup} that this
* {@link Crouton} should be added to.
* @param configuration The configuration for this crouton.
* @return The created {@link Crouton}.
*/
public static Crouton make(final Activity activity, final View customView, final int viewGroupResId,
final CroutonConfiguration configuration) {
return new Crouton(activity, customView, (ViewGroup) activity.findViewById(viewGroupResId), configuration);
}
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param customView The custom {@link View} to display
* @param viewGroup The {@link ViewGroup} that this {@link Crouton} should
* be added to.
* @return The created {@link Crouton}.
*/
public static Crouton make(final Activity activity, final View customView, final ViewGroup viewGroup) {
return new Crouton(activity, customView, viewGroup);
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity.
*
* @param activity The {@link Activity} that the {@link Crouton} should be
* attached to.
* @param text The text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @return The created {@link Crouton}.
*/
public static Crouton makeText(final Activity activity, final CharSequence text, final CroutonStyle style) {
return new Crouton(activity, text, style);
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param text The text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroupResId The resource id of the {@link ViewGroup} that this
* {@link Crouton} should be added to.
* @return The created {@link Crouton}.
*/
public static Crouton makeText(final Activity activity, final CharSequence text, final CroutonStyle style,
final int viewGroupResId) {
return new Crouton(activity, text, style, (ViewGroup) activity.findViewById(viewGroupResId));
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param text The text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroup The {@link ViewGroup} that this {@link Crouton} should
* be added to.
* @return The created {@link Crouton}.
*/
public static Crouton makeText(final Activity activity, final CharSequence text, final CroutonStyle style,
final ViewGroup viewGroup) {
return new Crouton(activity, text, style, viewGroup);
}
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity.
*
* @param activity The {@link Activity} that the {@link Crouton} should be
* attached to.
* @param textResourceId The resource id of the text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @return The created {@link Crouton}.
*/
public static Crouton makeText(final Activity activity, final int textResourceId, final CroutonStyle style) {
return makeText(activity, activity.getString(textResourceId), style);
}
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param textResourceId The resource id of the text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroupResId The resource id of the {@link ViewGroup} that this
* {@link Crouton} should be added to.
* @return The created {@link Crouton}.
*/
public static Crouton makeText(final Activity activity, final int textResourceId, final CroutonStyle style,
final int viewGroupResId) {
return makeText(activity, activity.getString(textResourceId), style,
(ViewGroup) activity.findViewById(viewGroupResId));
}
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param textResourceId The resource id of the text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroup The {@link ViewGroup} that this {@link Crouton} should
* be added to.
* @return The created {@link Crouton}.
*/
public static Crouton makeText(final Activity activity, final int textResourceId, final CroutonStyle style,
final ViewGroup viewGroup) {
return makeText(activity, activity.getString(textResourceId), style, viewGroup);
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity and displays it directly.
*
* @param activity The {@link android.app.Activity} that the {@link Crouton}
* should be attached to.
* @param customView The custom {@link View} to display
*/
public static void show(final Activity activity, final View customView) {
make(activity, customView).show();
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity and displays it directly.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param customView The custom {@link View} to display
* @param viewGroupResId The resource id of the {@link ViewGroup} that this
* {@link Crouton} should be added to.
*/
public static void show(final Activity activity, final View customView, final int viewGroupResId) {
make(activity, customView, viewGroupResId).show();
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity and displays it directly.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param customView The custom {@link View} to display
* @param viewGroup The {@link ViewGroup} that this {@link Crouton} should
* be added to.
*/
public static void show(final Activity activity, final View customView, final ViewGroup viewGroup) {
make(activity, customView, viewGroup).show();
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity and displays it directly.
*
* @param activity The {@link android.app.Activity} that the {@link Crouton}
* should be attached to.
* @param text The text you want to display.
* @param style The style that this {@link Crouton} should be created with.
*/
public static void showText(final Activity activity, final CharSequence text, final CroutonStyle style) {
makeText(activity, text, style).show();
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity and displays it directly.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param text The text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroupResId The resource id of the {@link ViewGroup} that this
* {@link Crouton} should be added to.
*/
public static void showText(final Activity activity, final CharSequence text, final CroutonStyle style,
final int viewGroupResId) {
makeText(activity, text, style, (ViewGroup) activity.findViewById(viewGroupResId)).show();
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity and displays it directly.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param text The text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroupResId The resource id of the {@link ViewGroup} that this
* {@link Crouton} should be added to.
* @param configuration The configuration for this Crouton.
*/
public static void showText(final Activity activity, final CharSequence text, final CroutonStyle style,
final int viewGroupResId, final CroutonConfiguration configuration) {
makeText(activity, text, style, (ViewGroup) activity.findViewById(viewGroupResId)).setConfiguration(
configuration).show();
}
/**
* Creates a {@link Crouton} with provided text and style for a given
* activity and displays it directly.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param text The text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroup The {@link ViewGroup} that this {@link Crouton} should
* be added to.
*/
public static void showText(final Activity activity, final CharSequence text, final CroutonStyle style,
final ViewGroup viewGroup) {
makeText(activity, text, style, viewGroup).show();
}
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity and displays it directly.
*
* @param activity The {@link Activity} that the {@link Crouton} should be
* attached to.
* @param textResourceId The resource id of the text you want to display.
* @param style The style that this {@link Crouton} should be created with.
*/
public static void showText(final Activity activity, final int textResourceId, final CroutonStyle style) {
showText(activity, activity.getString(textResourceId), style);
}
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity and displays it directly.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param textResourceId The resource id of the text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroupResId The resource id of the {@link ViewGroup} that this
* {@link Crouton} should be added to.
*/
public static void showText(final Activity activity, final int textResourceId, final CroutonStyle style,
final int viewGroupResId) {
showText(activity, activity.getString(textResourceId), style, viewGroupResId);
}
/**
* Creates a {@link Crouton} with provided text-resource and style for a
* given activity and displays it directly.
*
* @param activity The {@link Activity} that represents the context in which
* the Crouton should exist.
* @param textResourceId The resource id of the text you want to display.
* @param style The style that this {@link Crouton} should be created with.
* @param viewGroup The {@link ViewGroup} that this {@link Crouton} should
* be added to.
*/
public static void showText(final Activity activity, final int textResourceId, final CroutonStyle style,
final ViewGroup viewGroup) {
showText(activity, activity.getString(textResourceId), style, viewGroup);
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright 2013 Benjamin Weiss
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.keyboardsurfer.android.widget.crouton;
/**
* Allows configuring a {@link Crouton}s behavior aside from the actual view,
* which is defined via {@link CroutonStyle}.
* <p/>
* This allows to re-use a {@link CroutonStyle} while modifying parameters that
* only have to be applied when the {@link Crouton} is being displayed.
*
* @author chris
* @since 1.8
*/
public class CroutonConfiguration {
/**
* Display a {@link Crouton} for an infinite amount of time or until
* {@link de.keyboardsurfer.android.widget.crouton.Crouton#cancel()} has
* been called.
*/
public static final int DURATION_INFINITE = -1;
/** The default short display duration of a {@link Crouton}. */
public static final int DURATION_SHORT = 3000;
/** The default long display duration of a {@link Crouton}. */
public static final int DURATION_LONG = 5000;
/** The default {@link CroutonConfiguration} of a {@link Crouton}. */
public static final CroutonConfiguration DEFAULT;
static {
DEFAULT = new Builder().setDuration(DURATION_SHORT).build();
}
/**
* The durationInMilliseconds the {@link Crouton} will be displayed in
* milliseconds.
*/
final int durationInMilliseconds;
/** The resource id for the in animation. */
final int inAnimationResId;
/** The resource id for the out animation. */
final int outAnimationResId;
private CroutonConfiguration(final Builder builder) {
durationInMilliseconds = builder.durationInMilliseconds;
inAnimationResId = builder.inAnimationResId;
outAnimationResId = builder.outAnimationResId;
}
@Override
public String toString() {
return "Configuration{" + "durationInMilliseconds=" + durationInMilliseconds + ", inAnimationResId="
+ inAnimationResId + ", outAnimationResId=" + outAnimationResId + '}';
}
/** Creates a {@link Builder} to build a {@link CroutonConfiguration} upon. */
public static class Builder {
private int durationInMilliseconds = DURATION_SHORT;
private int inAnimationResId = 0;
private int outAnimationResId = 0;
/**
* Builds the {@link CroutonConfiguration}.
*
* @return The built {@link CroutonConfiguration}.
*/
public CroutonConfiguration build() {
return new CroutonConfiguration(this);
}
/**
* Set the durationInMilliseconds option of the {@link Crouton}.
*
* @param duration The durationInMilliseconds the crouton will be
* displayed {@link Crouton} in milliseconds.
* @return the {@link Builder}.
*/
public Builder setDuration(final int duration) {
durationInMilliseconds = duration;
return this;
}
/**
* The resource id for the in animation.
*
* @param inAnimationResId The resource identifier for the animation
* that's being shown when the {@link Crouton} is going to be
* displayed.
* @return the {@link Builder}.
*/
public Builder setInAnimation(final int inAnimationResId) {
this.inAnimationResId = inAnimationResId;
return this;
}
/**
* The resource id for the out animation
*
* @param outAnimationResId The resource identifier for the animation
* that's being shown when the {@link Crouton} is going to be
* removed.
* @return the {@link Builder}.
*/
public Builder setOutAnimation(final int outAnimationResId) {
this.outAnimationResId = outAnimationResId;
return this;
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2012 - 2013 Benjamin Weiss
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.keyboardsurfer.android.widget.crouton;
/** Provides callback methods on major lifecycle events of a {@link Crouton}. */
public interface CroutonLifecycleCallback {
/** Will be called when your Crouton has been displayed. */
public void onDisplayed();
/** Will be called when your {@link Crouton} has been removed. */
public void onRemoved();
// public void onCeasarDressing();
}

View File

@ -0,0 +1,366 @@
/*
* Copyright 2012 - 2013 Benjamin Weiss
* Copyright 2012 Neofonie Mobile GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.keyboardsurfer.android.widget.crouton;
import static org.mariotaku.twidere.util.Utils.announceForAccessibilityCompat;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.FrameLayout;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/** Manages the lifecycle of {@link Crouton}s. */
final class CroutonManager extends Handler {
private static CroutonManager INSTANCE;
private final Queue<Crouton> croutonQueue;
private CroutonManager() {
croutonQueue = new LinkedBlockingQueue<Crouton>();
}
/*
* (non-Javadoc)
*
* @see android.os.Handler#handleMessage(android.os.Message)
*/
@Override
public void handleMessage(final Message message) {
final Crouton crouton = (Crouton) message.obj;
switch (message.what) {
case Messages.DISPLAY_CROUTON: {
displayCrouton();
break;
}
case Messages.ADD_CROUTON_TO_VIEW: {
addCroutonToView(crouton);
break;
}
case Messages.REMOVE_CROUTON: {
removeCrouton(crouton);
if (null != crouton.getLifecycleCallback()) {
crouton.getLifecycleCallback().onRemoved();
}
break;
}
default: {
super.handleMessage(message);
break;
}
}
}
@Override
public String toString() {
return "CroutonManager{croutonQueue=" + croutonQueue + '}';
}
/**
* Removes the {@link Crouton}'s view after it's display
* durationInMilliseconds.
*
* @param crouton The {@link Crouton} added to a {@link ViewGroup} and
* should be removed.
*/
protected void removeCrouton(final Crouton crouton) {
final View croutonView = crouton.getView();
final ViewGroup croutonParentView = (ViewGroup) croutonView.getParent();
if (croutonParentView == null) return;
croutonView.startAnimation(crouton.getOutAnimation());
// Remove the Crouton from the queue.
final Crouton removed = croutonQueue.poll();
// Remove the crouton from the view's parent.
croutonParentView.removeView(croutonView);
if (null != removed) {
removed.detachActivity();
removed.detachViewGroup();
if (null != removed.getLifecycleCallback()) {
removed.getLifecycleCallback().onRemoved();
}
removed.detachLifecycleCallback();
}
// Send a message to display the next crouton but delay it by the out
// animation duration to make sure it finishes
sendMessageDelayed(crouton, Messages.DISPLAY_CROUTON, crouton.getOutAnimation().getDuration());
}
/**
* Adds a {@link Crouton} to the {@link ViewParent} of it's {@link Activity}
* .
*
* @param crouton The {@link Crouton} that should be added.
*/
private void addCroutonToView(final Crouton crouton) {
// don't add if it is already showing
if (crouton.isShowing()) return;
final View croutonView = crouton.getView();
if (null == croutonView.getParent()) {
ViewGroup.LayoutParams params = croutonView.getLayoutParams();
if (null == params) {
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
// display Crouton in ViewGroup is it has been supplied
if (null != crouton.getViewGroup()) {
// TODO implement add to last position feature (need to align
// with how this will be requested for activity)
if (crouton.getViewGroup() instanceof FrameLayout) {
crouton.getViewGroup().addView(croutonView, params);
} else {
crouton.getViewGroup().addView(croutonView, 0, params);
}
} else {
final Activity activity = crouton.getActivity();
if (null == activity || activity.isFinishing()) return;
activity.addContentView(croutonView, params);
}
}
croutonView.requestLayout(); // This is needed so the animation can use
// the measured with/height
croutonView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewTreeObserverAccessor.removeOnGlobalLayoutListener(croutonView.getViewTreeObserver(), this);
croutonView.startAnimation(crouton.getInAnimation());
announceForAccessibilityCompat(crouton.getActivity(), crouton.getView(), crouton.getText(), getClass());
if (CroutonConfiguration.DURATION_INFINITE != crouton.getConfiguration().durationInMilliseconds) {
sendMessageDelayed(crouton, Messages.REMOVE_CROUTON,
crouton.getConfiguration().durationInMilliseconds + crouton.getInAnimation().getDuration());
}
}
});
}
private long calculateCroutonDuration(final Crouton crouton) {
long croutonDuration = crouton.getConfiguration().durationInMilliseconds;
croutonDuration += crouton.getInAnimation().getDuration();
croutonDuration += crouton.getOutAnimation().getDuration();
return croutonDuration;
}
/** Displays the next {@link Crouton} within the queue. */
private void displayCrouton() {
if (croutonQueue.isEmpty()) return;
// First peek whether the Crouton has an activity.
final Crouton currentCrouton = croutonQueue.peek();
// If the activity is null we poll the Crouton off the queue.
if (null == currentCrouton.getActivity()) {
croutonQueue.poll();
}
if (!currentCrouton.isShowing()) {
// Display the Crouton
sendMessage(currentCrouton, Messages.ADD_CROUTON_TO_VIEW);
if (null != currentCrouton.getLifecycleCallback()) {
currentCrouton.getLifecycleCallback().onDisplayed();
}
} else {
sendMessageDelayed(currentCrouton, Messages.DISPLAY_CROUTON, calculateCroutonDuration(currentCrouton));
}
}
private void removeAllMessages() {
removeMessages(Messages.ADD_CROUTON_TO_VIEW);
removeMessages(Messages.DISPLAY_CROUTON);
removeMessages(Messages.REMOVE_CROUTON);
}
private void removeAllMessagesForCrouton(final Crouton crouton) {
removeMessages(Messages.ADD_CROUTON_TO_VIEW, crouton);
removeMessages(Messages.DISPLAY_CROUTON, crouton);
removeMessages(Messages.REMOVE_CROUTON, crouton);
}
/**
* Sends a {@link Crouton} within a {@link Message}.
*
* @param crouton The {@link Crouton} that should be sent.
* @param messageId The {@link Message} id.
*/
private void sendMessage(final Crouton crouton, final int messageId) {
final Message message = obtainMessage(messageId);
message.obj = crouton;
sendMessage(message);
}
/**
* Sends a {@link Crouton} within a delayed {@link Message}.
*
* @param crouton The {@link Crouton} that should be sent.
* @param messageId The {@link Message} id.
* @param delay The delay in milliseconds.
*/
private void sendMessageDelayed(final Crouton crouton, final int messageId, final long delay) {
final Message message = obtainMessage(messageId);
message.obj = crouton;
sendMessageDelayed(message, delay);
}
/**
* Inserts a {@link Crouton} to be displayed.
*
* @param crouton The {@link Crouton} to be displayed.
*/
void add(final Crouton crouton) {
croutonQueue.add(crouton);
displayCrouton();
}
/** Removes all {@link Crouton}s from the queue. */
void clearCroutonQueue() {
removeAllMessages();
if (croutonQueue == null) return;
// remove any views that may already have been added to the activity's
// content view
for (final Crouton crouton : croutonQueue) {
if (crouton.isShowing()) {
((ViewGroup) crouton.getView().getParent()).removeView(crouton.getView());
}
}
croutonQueue.clear();
}
/**
* Removes all {@link Crouton}s for the provided activity. This will remove
* crouton from {@link Activity}s content view immediately.
*/
void clearCroutonsForActivity(final Activity activity) {
if (croutonQueue == null) return;
final Iterator<Crouton> croutonIterator = croutonQueue.iterator();
while (croutonIterator.hasNext()) {
final Crouton crouton = croutonIterator.next();
if (null != crouton.getActivity() && crouton.getActivity().equals(activity)) {
// remove the crouton from the content view
if (crouton.isShowing()) {
((ViewGroup) crouton.getView().getParent()).removeView(crouton.getView());
}
removeAllMessagesForCrouton(crouton);
// remove the crouton from the queue
croutonIterator.remove();
}
}
}
/**
* Removes a {@link Crouton} immediately, even when it's currently being
* displayed.
*
* @param crouton The {@link Crouton} that should be removed.
*/
void removeCroutonImmediately(final Crouton crouton) {
// if Crouton has already been displayed then it may not be in the queue
// (because it was popped).
// This ensures the displayed Crouton is removed from its parent
// immediately, whether another instance
// of it exists in the queue or not.
// Note: crouton.isShowing() is false here even if it really is showing,
// as croutonView object in
// Crouton seems to be out of sync with reality!
if (null != crouton.getActivity() && null != crouton.getView() && null != crouton.getView().getParent()) {
((ViewGroup) crouton.getView().getParent()).removeView(crouton.getView());
// remove any messages pending for the crouton
removeAllMessagesForCrouton(crouton);
}
// remove any matching croutons from queue
if (croutonQueue == null) return;
final Iterator<Crouton> croutonIterator = croutonQueue.iterator();
while (croutonIterator.hasNext()) {
final Crouton c = croutonIterator.next();
if (c.equals(crouton) && null != c.getActivity()) {
// remove the crouton from the content view
if (crouton.isShowing()) {
((ViewGroup) c.getView().getParent()).removeView(c.getView());
}
// remove any messages pending for the crouton
removeAllMessagesForCrouton(c);
// remove the crouton from the queue
croutonIterator.remove();
// we have found our crouton so just break
break;
}
}
}
/** @return The currently used instance of the {@link Manager}. */
static synchronized CroutonManager getInstance() {
if (null == INSTANCE) {
INSTANCE = new CroutonManager();
}
return INSTANCE;
}
private static final class Messages {
public static final int DISPLAY_CROUTON = 0xc2007;
public static final int ADD_CROUTON_TO_VIEW = 0xc20074dd;
public static final int REMOVE_CROUTON = 0xc2007de1;
private Messages() { /* no-op */
}
}
private static class ViewTreeObserverAccessor {
@SuppressWarnings("deprecation")
private static void removeOnGlobalLayoutListener(final ViewTreeObserver observer,
final OnGlobalLayoutListener listener) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
observer.removeGlobalOnLayoutListener(listener);
} else {
ViewTreeObserverAccessorJB.removeOnGlobalLayoutListener(observer, listener);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private static class ViewTreeObserverAccessorJB {
private static void removeOnGlobalLayoutListener(final ViewTreeObserver observer,
final OnGlobalLayoutListener listener) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return;
observer.removeOnGlobalLayoutListener(listener);
}
}
}
}

View File

@ -0,0 +1,479 @@
/*
* Copyright 2012 - 2013 Benjamin Weiss
* Copyright 2012 Neofonie Mobile GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.keyboardsurfer.android.widget.crouton;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
/** The style for a {@link Crouton}. */
public class CroutonStyle {
public static final int holoRedLight = 0xffff4444;
public static final int holoGreenLight = 0xff99cc00;
public static final int holoBlueLight = 0xff33b5e5;
public static final int holoOrangeLight = 0xffffbb33;
/** Default style for alerting the user. */
public static final CroutonStyle ALERT;
/** Default style for warn the user. */
public static final CroutonStyle WARN;
/** Default style for confirming an action. */
public static final CroutonStyle CONFIRM;
/** Default style for general information. */
public static final CroutonStyle INFO;
static {
ALERT = new Builder().setBackgroundColorValue(holoRedLight).build();
WARN = new Builder().setBackgroundColorValue(holoOrangeLight).build();
CONFIRM = new Builder().setBackgroundColorValue(holoGreenLight).build();
INFO = new Builder().setBackgroundColorValue(holoBlueLight).build();
}
/**
* The {@link CroutonConfiguration} for this {@link CroutonStyle}. It can be
* overridden via {@link Crouton#setConfiguration(CroutonConfiguration)}.
*/
final CroutonConfiguration configuration;
/**
* The resource id of the backgroundResourceId.
* <p/>
* 0 for no backgroundResourceId.
*/
final int backgroundColorResourceId;
/**
* The resource id of the backgroundDrawableResourceId.
* <p/>
* 0 for no backgroundDrawableResourceId.
*/
final int backgroundDrawableResourceId;
/**
* The backgroundColorResourceValue's e.g. 0xffff4444;
* <p/>
* -1 for no value.
*/
final int backgroundColorValue;
/** Whether we should isTileEnabled the backgroundResourceId or not. */
final boolean isTileEnabled;
/**
* The text colorResourceId's resource id.
* <p/>
* 0 sets the text colorResourceId to the system theme default.
*/
final int textColorResourceId;
/** The height of the {@link Crouton} in pixels. */
final int heightInPixels;
/** Resource ID for the height of the {@link Crouton}. */
final int heightDimensionResId;
/** The width of the {@link Crouton} in pixels. */
final int widthInPixels;
/** Resource ID for the width of the {@link Crouton}. */
final int widthDimensionResId;
/** The text's gravity as provided by {@link Gravity}. */
final int gravity;
/** An additional image to display in the {@link Crouton}. */
final Drawable imageDrawable;
/** An additional image to display in the {@link Crouton}. */
final int imageResId;
/**
* The {@link ImageView.ScaleType} for the image to display in the
* {@link Crouton}.
*/
final ImageView.ScaleType imageScaleType;
/**
* The text size in sp
* <p/>
* 0 sets the text size to the system theme default
*/
final int textSize;
/** The text shadow color's resource id */
final int textShadowColorResId;
/** The text shadow radius */
final float textShadowRadius;
/** The text shadow vertical offset */
final float textShadowDy;
/** The text shadow horizontal offset */
final float textShadowDx;
/** The text appearance resource id for the text. */
final int textAppearanceResId;
/** The padding for the crouton view content in pixels */
final int paddingInPixels;
/** The resource id for the padding for the view content */
final int paddingDimensionResId;
private CroutonStyle(final Builder builder) {
configuration = builder.configuration;
backgroundColorResourceId = builder.backgroundColorResourceId;
backgroundDrawableResourceId = builder.backgroundDrawableResourceId;
isTileEnabled = builder.isTileEnabled;
textColorResourceId = builder.textColorResourceId;
heightInPixels = builder.heightInPixels;
heightDimensionResId = builder.heightDimensionResId;
widthInPixels = builder.widthInPixels;
widthDimensionResId = builder.widthDimensionResId;
gravity = builder.gravity;
imageDrawable = builder.imageDrawable;
textSize = builder.textSize;
textShadowColorResId = builder.textShadowColorResId;
textShadowRadius = builder.textShadowRadius;
textShadowDx = builder.textShadowDx;
textShadowDy = builder.textShadowDy;
textAppearanceResId = builder.textAppearanceResId;
imageResId = builder.imageResId;
imageScaleType = builder.imageScaleType;
paddingInPixels = builder.paddingInPixels;
paddingDimensionResId = builder.paddingDimensionResId;
backgroundColorValue = builder.backgroundColorValue;
}
@Override
public String toString() {
return "Style{" + "configuration=" + configuration + ", backgroundColorResourceId=" + backgroundColorResourceId
+ ", backgroundDrawableResourceId=" + backgroundDrawableResourceId + ", backgroundColorValue="
+ backgroundColorValue + ", isTileEnabled=" + isTileEnabled + ", textColorResourceId="
+ textColorResourceId + ", heightInPixels=" + heightInPixels + ", heightDimensionResId="
+ heightDimensionResId + ", widthInPixels=" + widthInPixels + ", widthDimensionResId="
+ widthDimensionResId + ", gravity=" + gravity + ", imageDrawable=" + imageDrawable + ", imageResId="
+ imageResId + ", imageScaleType=" + imageScaleType + ", textSize=" + textSize
+ ", textShadowColorResId=" + textShadowColorResId + ", textShadowRadius=" + textShadowRadius
+ ", textShadowDy=" + textShadowDy + ", textShadowDx=" + textShadowDx + ", textAppearanceResId="
+ textAppearanceResId + ", paddingInPixels=" + paddingInPixels + ", paddingDimensionResId="
+ paddingDimensionResId + '}';
}
/** Builder for the {@link CroutonStyle} object. */
public static class Builder {
private CroutonConfiguration configuration;
private int backgroundColorValue;
private int backgroundColorResourceId;
private int backgroundDrawableResourceId;
private boolean isTileEnabled;
private int textColorResourceId;
private int heightInPixels;
private int heightDimensionResId;
private int widthInPixels;
private int widthDimensionResId;
private int gravity;
private Drawable imageDrawable;
private int textSize;
private int textShadowColorResId;
private float textShadowRadius;
private float textShadowDx;
private float textShadowDy;
private int textAppearanceResId;
private int imageResId;
private ImageView.ScaleType imageScaleType;
private int paddingInPixels;
private int paddingDimensionResId;
/** Creates a {@link Builder} to build a {@link CroutonStyle} upon. */
public Builder() {
configuration = CroutonConfiguration.DEFAULT;
paddingInPixels = 10;
backgroundColorResourceId = android.R.color.holo_blue_light;
backgroundDrawableResourceId = 0;
backgroundColorValue = -1;
isTileEnabled = false;
textColorResourceId = android.R.color.white;
heightInPixels = LayoutParams.WRAP_CONTENT;
widthInPixels = LayoutParams.MATCH_PARENT;
gravity = Gravity.CENTER;
imageDrawable = null;
imageResId = 0;
imageScaleType = ImageView.ScaleType.FIT_XY;
}
/**
* Creates a {@link Builder} to build a {@link CroutonStyle} upon.
*
* @param baseStyle The base {@link CroutonStyle} to use for this
* {@link CroutonStyle} .
*/
public Builder(final CroutonStyle baseStyle) {
configuration = baseStyle.configuration;
backgroundColorValue = baseStyle.backgroundColorValue;
backgroundColorResourceId = baseStyle.backgroundColorResourceId;
backgroundDrawableResourceId = baseStyle.backgroundDrawableResourceId;
isTileEnabled = baseStyle.isTileEnabled;
textColorResourceId = baseStyle.textColorResourceId;
heightInPixels = baseStyle.heightInPixels;
heightDimensionResId = baseStyle.heightDimensionResId;
widthInPixels = baseStyle.widthInPixels;
widthDimensionResId = baseStyle.widthDimensionResId;
gravity = baseStyle.gravity;
imageDrawable = baseStyle.imageDrawable;
textSize = baseStyle.textSize;
textShadowColorResId = baseStyle.textShadowColorResId;
textShadowRadius = baseStyle.textShadowRadius;
textShadowDx = baseStyle.textShadowDx;
textShadowDy = baseStyle.textShadowDy;
textAppearanceResId = baseStyle.textAppearanceResId;
imageResId = baseStyle.imageResId;
imageScaleType = baseStyle.imageScaleType;
paddingInPixels = baseStyle.paddingInPixels;
paddingDimensionResId = baseStyle.paddingDimensionResId;
}
/** @return a configured {@link CroutonStyle} object. */
public CroutonStyle build() {
return new CroutonStyle(this);
}
/**
* Set the backgroundColorResourceId option of the {@link Crouton}.
*
* @param backgroundColorResourceId The backgroundColorResourceId's
* resource id.
* @return the {@link Builder}.
*/
public Builder setBackgroundColor(final int backgroundColorResourceId) {
this.backgroundColorResourceId = backgroundColorResourceId;
return this;
}
/**
* Set the backgroundColorResourceValue option of the {@link Crouton}.
*
* @param backgroundColorValue The backgroundColorResourceValue's e.g.
* 0xffff4444;
* @return the {@link Builder}.
*/
public Builder setBackgroundColorValue(final int backgroundColorValue) {
this.backgroundColorValue = backgroundColorValue;
return this;
}
/**
* Set the backgroundDrawableResourceId option for the {@link Crouton}.
*
* @param backgroundDrawableResourceId Resource ID of a
* backgroundDrawableResourceId image drawable.
* @return the {@link Builder}.
*/
public Builder setBackgroundDrawable(final int backgroundDrawableResourceId) {
this.backgroundDrawableResourceId = backgroundDrawableResourceId;
return this;
}
/**
* Set the {@link CroutonConfiguration} option of the {@link Crouton}.
*
* @param configuration The {@link CroutonConfiguration}.
* @return the {@link Builder}.
*/
public Builder setConfiguration(final CroutonConfiguration configuration) {
this.configuration = configuration;
return this;
}
/**
* Set the gravity option for the {@link Crouton}.
*
* @param gravity The text's gravity as provided by {@link Gravity}.
* @return the {@link Builder}.
*/
public Builder setGravity(final int gravity) {
this.gravity = gravity;
return this;
}
/**
* Set the heightInPixels option for the {@link Crouton}.
*
* @param height The height of the {@link Crouton} in pixel. Can also be
* {@link LayoutParams#MATCH_PARENT} or
* {@link LayoutParams#WRAP_CONTENT}.
* @return the {@link Builder}.
*/
public Builder setHeight(final int height) {
heightInPixels = height;
return this;
}
/**
* Set the resource id for the height option for the {@link Crouton}.
*
* @param heightDimensionResId Resource ID of a dimension for the height
* of the {@link Crouton}.
* @return the {@link Builder}.
*/
public Builder setHeightDimensionResId(final int heightDimensionResId) {
this.heightDimensionResId = heightDimensionResId;
return this;
}
/**
* Set the image option for the {@link Crouton}.
*
* @param imageDrawable An additional image to display in the
* {@link Crouton}.
* @return the {@link Builder}.
*/
public Builder setImageDrawable(final Drawable imageDrawable) {
this.imageDrawable = imageDrawable;
return this;
}
/**
* Set the image resource option for the {@link Crouton}.
*
* @param imageResId An additional image to display in the
* {@link Crouton}.
* @return the {@link Builder}.
*/
public Builder setImageResource(final int imageResId) {
this.imageResId = imageResId;
return this;
}
/** The {@link android.widget.ImageView.ScaleType} for the image. */
public Builder setImageScaleType(final ImageView.ScaleType imageScaleType) {
this.imageScaleType = imageScaleType;
return this;
}
/** The resource id for the padding for the crouton view's content. */
public Builder setPaddingDimensionResId(final int paddingResId) {
paddingDimensionResId = paddingResId;
return this;
}
/** The padding for the crouton view's content in pixels. */
public Builder setPaddingInPixels(final int padding) {
paddingInPixels = padding;
return this;
}
/** The text appearance resource id for the text. */
public Builder setTextAppearance(final int textAppearanceResId) {
this.textAppearanceResId = textAppearanceResId;
return this;
}
/**
* Set the textColorResourceId option for the {@link Crouton}.
*
* @param textColor The resource id of the text colorResourceId.
* @return the {@link Builder}.
*/
public Builder setTextColor(final int textColor) {
textColorResourceId = textColor;
return this;
}
/** The text shadow color resource id. */
public Builder setTextShadowColor(final int textShadowColorResId) {
this.textShadowColorResId = textShadowColorResId;
return this;
}
/** The text shadow horizontal offset. */
public Builder setTextShadowDx(final float textShadowDx) {
this.textShadowDx = textShadowDx;
return this;
}
/** The text shadow vertical offset. */
public Builder setTextShadowDy(final float textShadowDy) {
this.textShadowDy = textShadowDy;
return this;
}
/** The text shadow radius. */
public Builder setTextShadowRadius(final float textShadowRadius) {
this.textShadowRadius = textShadowRadius;
return this;
}
/** The text size in sp. */
public Builder setTextSize(final int textSize) {
this.textSize = textSize;
return this;
}
/**
* Set the isTileEnabled option for the {@link Crouton}.
*
* @param isTileEnabled <code>true</code> if you want the
* backgroundResourceId to be tiled, else <code>false</code>.
* @return the {@link Builder}.
*/
public Builder setTileEnabled(final boolean isTileEnabled) {
this.isTileEnabled = isTileEnabled;
return this;
}
/**
* Set the widthInPixels option for the {@link Crouton}.
*
* @param width The width of the {@link Crouton} in pixel. Can also be
* {@link LayoutParams#MATCH_PARENT} or
* {@link LayoutParams#WRAP_CONTENT}.
* @return the {@link Builder}.
*/
public Builder setWidth(final int width) {
widthInPixels = width;
return this;
}
/**
* Set the resource id for the width option for the {@link Crouton}.
*
* @param widthDimensionResId Resource ID of a dimension for the width
* of the {@link Crouton}.
* @return the {@link Builder}.
*/
public Builder setWidthDimensionResId(final int widthDimensionResId) {
this.widthDimensionResId = widthDimensionResId;
return this;
}
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2012 - 2013 Benjamin Weiss
* Copyright 2012 Neofonie Mobile GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.keyboardsurfer.android.widget.crouton;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
/** Builds the default animations for showing and hiding a {@link Crouton}. */
final class DefaultAnimationsBuilder {
private static final long DURATION = 400;
private static Animation slideInDownAnimation, slideOutUpAnimation;
private static int lastInAnimationHeight, lastOutAnimationHeight;
private DefaultAnimationsBuilder() {
/* no-op */
}
private static boolean areLastMeasuredAnimationHeightAndCurrentEqual(final int lastHeight, final View croutonView) {
return lastHeight == croutonView.getMeasuredHeight();
}
private static boolean areLastMeasuredInAnimationHeightAndCurrentEqual(final View croutonView) {
return areLastMeasuredAnimationHeightAndCurrentEqual(lastInAnimationHeight, croutonView);
}
private static boolean areLastMeasuredOutAnimationHeightAndCurrentEqual(final View croutonView) {
return areLastMeasuredAnimationHeightAndCurrentEqual(lastOutAnimationHeight, croutonView);
}
private static void setLastInAnimationHeight(final int lastInAnimationHeight) {
DefaultAnimationsBuilder.lastInAnimationHeight = lastInAnimationHeight;
}
private static void setLastOutAnimationHeight(final int lastOutAnimationHeight) {
DefaultAnimationsBuilder.lastOutAnimationHeight = lastOutAnimationHeight;
}
/**
* @param croutonView The croutonView which gets animated.
* @return The default Animation for a showing {@link Crouton}.
*/
static Animation buildDefaultSlideInDownAnimation(final View croutonView) {
if (!areLastMeasuredInAnimationHeightAndCurrentEqual(croutonView) || null == slideInDownAnimation) {
slideInDownAnimation = new TranslateAnimation(0, 0, // X: from, to
-croutonView.getMeasuredHeight(), 0 // Y: from, to
);
slideInDownAnimation.setDuration(DURATION);
setLastInAnimationHeight(croutonView.getMeasuredHeight());
}
return slideInDownAnimation;
}
/**
* @param croutonView The croutonView which gets animated.
* @return The default Animation for a hiding {@link Crouton}.
*/
static Animation buildDefaultSlideOutUpAnimation(final View croutonView) {
if (!areLastMeasuredOutAnimationHeightAndCurrentEqual(croutonView) || null == slideOutUpAnimation) {
slideOutUpAnimation = new TranslateAnimation(0, 0, // X: from, to
0, -croutonView.getMeasuredHeight() // Y: from, to
);
slideOutUpAnimation.setDuration(DURATION);
setLastOutAnimationHeight(croutonView.getMeasuredHeight());
}
return slideOutUpAnimation;
}
}

View File

@ -0,0 +1,19 @@
package edu.ucdavis.earlybird;
import java.io.File;
import java.io.FileFilter;
public final class CSVFileFilter implements FileFilter {
@Override
public boolean accept(final File file) {
return file.isFile() && "csv".equalsIgnoreCase(getExtension(file));
}
static String getExtension(final File file) {
final String name = file.getName();
final int pos = name.lastIndexOf('.');
if (pos == -1) return null;
return name.substring(pos + 1);
}
}

View File

@ -0,0 +1,70 @@
package edu.ucdavis.earlybird;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.BatteryManager;
import android.util.Log;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.util.Utils;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class ProfilingUtil {
public static final String FILE_NAME_PROFILE = "Profile";
public static final String FILE_NAME_LOCATION = "Location";
public static final String FILE_NAME_APP = "App";
public static final String FILE_NAME_WIFI = "Wifi";
public static boolean isCharging(final Context context) {
if (context == null) return false;
final Intent intent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
if (intent == null) return false;
final int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB;
}
public static boolean log(final Context context, final String msg) {
if (Utils.isDebuggable(context)) {
final StackTraceElement ste = new Throwable().fillInStackTrace().getStackTrace()[1];
final String fullname = ste.getClassName();
final String name = fullname.substring(fullname.lastIndexOf('.'));
final String tag = name + "." + ste.getMethodName();
Log.d(tag, msg);
return true;
} else
return false;
}
public static void profile(final Context context, final long accountID, final String text) {
profile(context, accountID + "_" + FILE_NAME_PROFILE, text);
}
public static void profile(final Context context, final String name, final String text) {
if (context == null) return;
final SharedPreferences prefs = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE);
if (!prefs.getBoolean(Constants.KEY_UCD_DATA_PROFILING, false)) return;
final String filename = name + ".csv";
new Thread() {
@Override
public void run() {
try {
final FileOutputStream fos = context.openFileOutput(filename, Context.MODE_APPEND);
if (fos == null) return;
final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
bw.write("[" + System.currentTimeMillis() + "], " + text + "\n");
bw.flush();
fos.close();
} catch (final Exception e) {
e.printStackTrace();
}
};
}.start();
}
}

View File

@ -0,0 +1,84 @@
package edu.ucdavis.earlybird;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.location.LocationManager;
import android.os.IBinder;
/**
* Request location ONCE per WAKE_PERIOD_IN_MILLI.
*/
public class UCDService extends Service {
public static final long LOCATION_PERIOD_IN_MILLI = 15 * 60 * 1000;
public static final String ACTION_GET_LOCATION = "edu.ucdavis.earlybird.GET_LOCATION";
private LocationManager mLocationManager;
private AlarmManager mAlarmManager;
private LocationUpdateReceiver mAlarmReceiver;
private PendingIntent locationIntent;
private PendingIntent uploadIntent;
@Override
public IBinder onBind(final Intent intent) {
throw new IllegalStateException("Not implemented.");
}
@Override
public void onCreate() {
super.onCreate();
ProfilingUtil.log(this, "onCreate");
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mAlarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
mAlarmReceiver = new LocationUpdateReceiver();
final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_GET_LOCATION);
registerReceiver(mAlarmReceiver, filter);
final Intent intent = new Intent(ACTION_GET_LOCATION);
locationIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), LOCATION_PERIOD_IN_MILLI,
locationIntent);
// Upload Service
final Intent i = new Intent(UploadReceiver.ACTION_UPLOAD_PROFILE);
uploadIntent = PendingIntent.getBroadcast(this, 0, i, 0);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 12 * 60 * 60 * 1000,
uploadIntent);
}
@Override
public void onDestroy() {
mAlarmManager.cancel(locationIntent);
unregisterReceiver(mAlarmReceiver);
super.onDestroy();
}
private final class LocationUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
if (mLocationManager == null) return;
ProfilingUtil.log(context, "AlarmReceiver");
final String provider = LocationManager.NETWORK_PROVIDER;
if (mLocationManager.isProviderEnabled(provider)) {
final Location location = mLocationManager.getLastKnownLocation(provider);
if (location != null) {
ProfilingUtil.profile(UCDService.this, ProfilingUtil.FILE_NAME_LOCATION, location.getTime() + ","
+ location.getLatitude() + "," + location.getLongitude() + "," + location.getProvider());
ProfilingUtil.log(context,
location.getTime() + "," + location.getLatitude() + "," + location.getLongitude() + ","
+ location.getProvider());
}
}
}
}
}

View File

@ -0,0 +1,27 @@
package edu.ucdavis.earlybird;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import org.mariotaku.twidere.util.Utils;
public class UploadReceiver extends BroadcastReceiver {
public static final String ACTION_UPLOAD_PROFILE = "edu.ucdavis.earlybird.UPLOAD_PROFILE";
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
final boolean isWifi = Utils.isOnWifi(context.getApplicationContext());
final boolean isCharging = ProfilingUtil.isCharging(context.getApplicationContext());
if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) {
final boolean wifi = intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false);
ProfilingUtil.profile(context, ProfilingUtil.FILE_NAME_WIFI, wifi ? "connected" : "disconnected");
}
if (isWifi && isCharging) {
new UploadTask(context).execute();
}
}
}

View File

@ -0,0 +1,136 @@
package edu.ucdavis.earlybird;
import static org.mariotaku.twidere.util.Utils.copyStream;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.provider.Settings.Secure;
import twitter4j.TwitterException;
import twitter4j.http.HttpClientWrapper;
import twitter4j.http.HttpParameter;
import twitter4j.http.HttpResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class UploadTask extends AsyncTask<Void, Void, Void> {
private static final String LAST_UPLOAD_DATE = "last_upload_time";
private static final double MILLSECS_HALF_DAY = 1000 * 60 * 60 * 12;
private final String device_id;
private final Context context;
private final HttpClientWrapper client = new HttpClientWrapper();
private static final String PROFILE_SERVER_URL = "http://weik.metaisle.com/profiles";
// private static final String PROFILE_SERVER_URL =
// "http://192.168.0.105:3000/profiles";
public UploadTask(final Context context) {
this.context = context;
device_id = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
}
public void uploadMultipart(final String url, final File file) {
final String app_root = file.getParent();
final File tmp_dir = new File(app_root + "/tmp");
if (!tmp_dir.exists()) {
if (!tmp_dir.mkdirs()) {
ProfilingUtil.log(context, "cannot create tmp, do nothing.");
return;
}
}
final File tmp = new File(tmp_dir, file.getName());
file.renameTo(tmp);
try {
final HttpParameter param = new HttpParameter("upload", tmp);
final HttpResponse resp = client.post(url, null, new HttpParameter[] { param });
// Responses from the server (code and message)
final int serverResponseCode = resp.getStatusCode();
ProfilingUtil.log(context, "server response code " + serverResponseCode);
if (serverResponseCode / 100 == 2) {
tmp.delete();
} else {
putBackProfile(context, tmp, file);
}
} catch (final TwitterException e) {
e.printStackTrace();
putBackProfile(context, tmp, file);
}
}
@Override
protected Void doInBackground(final Void... params) {
final SharedPreferences prefs = context.getSharedPreferences("ucd_data_profiling", Context.MODE_PRIVATE);
if (prefs.contains(LAST_UPLOAD_DATE)) {
final long lastUpload = prefs.getLong(LAST_UPLOAD_DATE, System.currentTimeMillis());
final double deltaDays = (System.currentTimeMillis() - lastUpload) / (MILLSECS_HALF_DAY * 2);
if (deltaDays < 1) {
ProfilingUtil.log(context, "Uploaded less than 1 day ago.");
return null;
}
}
final File root = context.getFilesDir();
final File[] files = root.listFiles(new CSVFileFilter());
uploadToNode(files);
prefs.edit().putLong(LAST_UPLOAD_DATE, System.currentTimeMillis()).commit();
return null;
}
private boolean uploadToNode(final File... files) {
for (final File file : files) {
if (file.isDirectory()) {
continue;
}
final String url = PROFILE_SERVER_URL + "/" + device_id + "/"
+ file.getName().replaceFirst("[.][^.]+$", "");
ProfilingUtil.log(context, url);
uploadMultipart(url, file);
}
return false;
}
public static void putBackProfile(final Context context, final File tmp, final File profile) {
boolean success;
if (profile.exists()) {
try {
final FileOutputStream os = new FileOutputStream(tmp, true);
final FileInputStream is = new FileInputStream(profile);
copyStream(is, os);
is.close();
os.close();
} catch (final IOException e) {
e.printStackTrace();
success = false;
}
success = true;
if (success && tmp.renameTo(profile) && tmp.delete()) {
ProfilingUtil.log(context, "put profile back success");
} else {
ProfilingUtil.log(context, "put profile back failed");
}
} else {
if (tmp.renameTo(profile)) {
ProfilingUtil.log(context, "put profile back success");
} else {
ProfilingUtil.log(context, "put profile back failed");
}
}
}
}

View File

@ -0,0 +1,249 @@
package it.sephiroth.android.library.imagezoom;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import it.sephiroth.android.library.imagezoom.ScaleGestureDetector.OnScaleGestureListener;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
public class ImageViewTouch extends ImageViewTouchBase implements Constants {
private static final float SCROLL_DELTA_THRESHOLD = 1.0f;
static final float MIN_ZOOM = 0.9f;
protected int mTouchSlop;
protected float mCurrentScaleFactor;
protected float mScaleFactor;
protected int mDoubleTapDirection;
protected GestureDetector mGestureDetector;
protected OnGestureListener mGestureListener;
protected OnScaleGestureListener mScaleListener;
protected ScaleGestureDetector mScaleDetector;
protected boolean mDoubleTapToZoomEnabled = true;
protected boolean mScaleEnabled = true;
protected boolean mScrollEnabled = true;
private OnImageViewTouchDoubleTapListener doubleTapListener;
public ImageViewTouch(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
/**
* Determines whether this ImageViewTouch can be scrolled.
*
* @param direction - positive direction value means scroll from right to
* left, negative value means scroll from left to right
* @return true if there is some more place to scroll, false - otherwise.
*/
public boolean canScroll(final int direction) {
final RectF bitmapRect = getBitmapRect();
updateRect(bitmapRect, mScrollRect);
final Rect imageViewRect = new Rect();
getGlobalVisibleRect(imageViewRect);
if (bitmapRect.right >= imageViewRect.right) {
if (direction < 0) return Math.abs(bitmapRect.right - imageViewRect.right) > SCROLL_DELTA_THRESHOLD;
}
final double bitmapScrollRectDelta = Math.abs(bitmapRect.left - mScrollRect.left);
return bitmapScrollRectDelta > SCROLL_DELTA_THRESHOLD;
}
public boolean getDoubleTapEnabled() {
return mDoubleTapToZoomEnabled;
}
@Override
public boolean onTouchEvent(final MotionEvent event) {
if (mScaleDetector != null) {
mScaleDetector.onTouchEvent(event);
}
if (mScaleDetector != null && !mScaleDetector.isInProgress()) {
mGestureDetector.onTouchEvent(event);
}
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
if (getScale() < 1f) {
zoomTo(1f, 50);
}
break;
}
return true;
}
public void setDoubleTapListener(final OnImageViewTouchDoubleTapListener doubleTapListener) {
this.doubleTapListener = doubleTapListener;
}
public void setDoubleTapToZoomEnabled(final boolean value) {
mDoubleTapToZoomEnabled = value;
}
public void setScaleEnabled(final boolean value) {
mScaleEnabled = value;
}
public void setScrollEnabled(final boolean value) {
mScrollEnabled = value;
}
@Override
protected void _setImageDrawable(final Drawable drawable, final boolean reset, final Matrix initial_matrix,
final float maxZoom) {
super._setImageDrawable(drawable, reset, initial_matrix, maxZoom);
mScaleFactor = getMaxZoom() / 3;
}
protected OnGestureListener getGestureListener() {
return new GestureListener();
}
protected OnScaleGestureListener getScaleListener() {
return new ScaleListener();
}
@Override
protected void init() {
super.init();
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
mGestureListener = getGestureListener();
mGestureDetector = new GestureDetector(getContext(), mGestureListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
mScaleListener = getScaleListener();
mScaleDetector = new ScaleGestureDetector(getContext(), mScaleListener);
}
mCurrentScaleFactor = 1f;
mDoubleTapDirection = 1;
}
@Override
protected void onBitmapChanged(final Drawable drawable) {
super.onBitmapChanged(drawable);
final float v[] = new float[9];
mSuppMatrix.getValues(v);
mCurrentScaleFactor = v[Matrix.MSCALE_X];
}
protected float onDoubleTapPost(final float scale, final float maxZoom) {
if (mDoubleTapDirection == 1) {
if (scale + mScaleFactor * 2 <= maxZoom)
return scale + mScaleFactor;
else {
mDoubleTapDirection = -1;
return maxZoom;
}
} else {
mDoubleTapDirection = 1;
return 1f;
}
}
@Override
protected void onZoom(final float scale) {
super.onZoom(scale);
if (mScaleDetector != null && !mScaleDetector.isInProgress()) {
mCurrentScaleFactor = scale;
}
}
public class GestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(final MotionEvent e) {
if (BuildConfig.DEBUG) {
Log.i(LOG_TAG, "onDoubleTap. double tap enabled? " + mDoubleTapToZoomEnabled);
}
if (mDoubleTapToZoomEnabled) {
final float scale = getScale();
float targetScale = scale;
targetScale = onDoubleTapPost(scale, getMaxZoom());
targetScale = Math.min(getMaxZoom(), Math.max(targetScale, MIN_ZOOM));
mCurrentScaleFactor = targetScale;
zoomTo(targetScale, e.getX(), e.getY(), 200);
invalidate();
}
if (null != doubleTapListener) {
doubleTapListener.onDoubleTap();
}
return super.onDoubleTap(e);
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) {
if (!mScrollEnabled) return false;
// if (e1.getPointerCount() > 1 || e2.getPointerCount() > 1) return
// false;
if (mScaleDetector != null && mScaleDetector.isInProgress()) return false;
final float diffX = e2.getX() - e1.getX();
final float diffY = e2.getY() - e1.getY();
if (Math.abs(velocityX) > 800 || Math.abs(velocityY) > 800) {
scrollBy(diffX / 2, diffY / 2, 300);
invalidate();
}
return super.onFling(e1, e2, velocityX, velocityY);
}
@Override
public void onLongPress(final MotionEvent e) {
if (isLongClickable()) {
if (mScaleDetector != null && !mScaleDetector.isInProgress()) {
setPressed(true);
performLongClick();
}
}
}
@Override
public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY) {
if (!mScrollEnabled) return false;
if (e1 == null || e2 == null) return false;
// if (e1.getPointerCount() > 1 || e2.getPointerCount() > 1) return
// false;
if (mScaleDetector != null && mScaleDetector.isInProgress()) return false;
if (getScale() == 1f) return false;
scrollBy(-distanceX, -distanceY);
invalidate();
return super.onScroll(e1, e2, distanceX, distanceY);
}
}
public interface OnImageViewTouchDoubleTapListener {
void onDoubleTap();
}
public class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(final ScaleGestureDetector detector) {
float targetScale = mCurrentScaleFactor * detector.getScaleFactor();
if (mScaleEnabled) {
targetScale = Math.min(getMaxZoom(), Math.max(targetScale, MIN_ZOOM));
zoomTo(targetScale, detector.getFocusX(), detector.getFocusY());
mCurrentScaleFactor = Math.min(getMaxZoom(), Math.max(targetScale, MIN_ZOOM));
mDoubleTapDirection = 1;
invalidate();
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,504 @@
package it.sephiroth.android.library.imagezoom;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
import it.sephiroth.android.library.imagezoom.easing.Cubic;
import it.sephiroth.android.library.imagezoom.easing.Easing;
import it.sephiroth.android.library.imagezoom.graphics.FastBitmapDrawable;
import it.sephiroth.android.library.imagezoom.utils.IDisposable;
/**
* Base View to manage image zoom/scrool/pinch operations
*
* @author alessandro
*/
public class ImageViewTouchBase extends ImageView implements IDisposable {
public static final String LOG_TAG = "image";
protected Easing mEasing = new Cubic();
protected Matrix mBaseMatrix = new Matrix();
protected Matrix mSuppMatrix = new Matrix();
protected Handler mHandler = new Handler();
protected Runnable mOnLayoutRunnable = null;
protected float mMaxZoom;
protected final Matrix mDisplayMatrix = new Matrix();
protected final float[] mMatrixValues = new float[9];
protected int mThisWidth = -1, mThisHeight = -1;
protected boolean mFitToScreen = false;
final protected float MAX_ZOOM = 2.0f;
protected RectF mBitmapRect = new RectF();
protected RectF mCenterRect = new RectF();
protected RectF mScrollRect = new RectF();
private OnBitmapChangedListener mListener;
public ImageViewTouchBase(final Context context) {
super(context);
init();
}
public ImageViewTouchBase(final Context context, final AttributeSet attrs) {
super(context, attrs);
init();
}
public void clear() {
setImageBitmap(null, true);
}
@Override
public void dispose() {
clear();
}
/**
* Returns the current image display matrix. This matrix can be used in the
* next call to the {@link #setImageBitmap(Bitmap, boolean, Matrix)} to
* restore the same view state of the previous {@link Bitmap}
*
* @return
*/
public Matrix getDisplayMatrix() {
return new Matrix(mSuppMatrix);
}
public Matrix getImageViewMatrix() {
mDisplayMatrix.set(mBaseMatrix);
mDisplayMatrix.postConcat(mSuppMatrix);
return mDisplayMatrix;
}
public float getMaxZoom() {
return mMaxZoom;
}
@Override
public float getRotation() {
return 0;
}
public float getScale() {
return getScale(mSuppMatrix);
}
public void scrollBy(final float x, final float y) {
panBy(x, y);
}
public void setFitToScreen(final boolean value) {
if (value != mFitToScreen) {
mFitToScreen = value;
requestLayout();
}
}
@Override
public void setImageBitmap(final Bitmap bm) {
setImageBitmap(bm, true);
}
/**
* Set the new image to display and reset the internal matrix.
*
* @param bitmap - the {@link Bitmap} to display
* @param reset - if true the image bounds will be recreated, otherwise the
* old {@link Matrix} is used to display the new bitmap
* @see #setImageBitmap(Bitmap)
*/
public void setImageBitmap(final Bitmap bitmap, final boolean reset) {
setImageBitmap(bitmap, reset, null);
}
/**
* Similar to {@link #setImageBitmap(Bitmap, boolean)} but an optional view
* {@link Matrix} can be passed to determine the new bitmap view matrix.<br />
* This method is useful if you need to restore a Bitmap with the same
* zoom/pan values from a previous state
*
* @param bitmap - the {@link Bitmap} to display
* @param reset
* @param matrix - the {@link Matrix} to be used to display the new bitmap
* @see #setImageBitmap(Bitmap, boolean)
* @see #setImageBitmap(Bitmap)
* @see #getImageViewMatrix()
* @see #getDisplayMatrix()
*/
public void setImageBitmap(final Bitmap bitmap, final boolean reset, final Matrix matrix) {
setImageBitmap(bitmap, reset, matrix, -1);
}
/**
* @param bitmap
* @param reset
* @param matrix
* @param maxZoom - maximum zoom allowd during zoom gestures
* @see #setImageBitmap(Bitmap, boolean, Matrix)
*/
public void setImageBitmap(final Bitmap bitmap, final boolean reset, final Matrix matrix, final float maxZoom) {
Log.i(LOG_TAG, "setImageBitmap: " + bitmap);
if (bitmap != null) {
setImageDrawable(new FastBitmapDrawable(bitmap), reset, matrix, maxZoom);
} else {
setImageDrawable(null, reset, matrix, maxZoom);
}
}
@Override
public void setImageDrawable(final Drawable drawable) {
setImageDrawable(drawable, true, null, -1);
}
public void setImageDrawable(final Drawable drawable, final boolean reset, final Matrix initial_matrix,
final float maxZoom) {
final int viewWidth = getWidth();
if (viewWidth <= 0) {
mOnLayoutRunnable = new Runnable() {
@Override
public void run() {
setImageDrawable(drawable, reset, initial_matrix, maxZoom);
}
};
return;
}
_setImageDrawable(drawable, reset, initial_matrix, maxZoom);
}
@Override
public void setImageResource(final int resId) {
setImageDrawable(getContext().getResources().getDrawable(resId));
}
public void setOnBitmapChangedListener(final OnBitmapChangedListener listener) {
mListener = listener;
}
public void zoomTo(final float scale, final float durationMs) {
final float cx = getWidth() / 2F;
final float cy = getHeight() / 2F;
zoomTo(scale, cx, cy, durationMs);
}
protected void _setImageDrawable(final Drawable drawable, final boolean reset, final Matrix initial_matrix,
final float maxZoom) {
if (drawable != null) {
if (mFitToScreen) {
getProperBaseMatrix2(drawable, mBaseMatrix);
} else {
getProperBaseMatrix(drawable, mBaseMatrix);
}
super.setImageDrawable(drawable);
} else {
mBaseMatrix.reset();
super.setImageDrawable(null);
}
if (reset) {
mSuppMatrix.reset();
if (initial_matrix != null) {
mSuppMatrix = new Matrix(initial_matrix);
}
}
setImageMatrix(getImageViewMatrix());
if (maxZoom < 1) {
mMaxZoom = maxZoom();
} else {
mMaxZoom = maxZoom;
}
onBitmapChanged(drawable);
}
protected void center(final boolean horizontal, final boolean vertical) {
// Log.i(LOG_TAG, "center");
final Drawable drawable = getDrawable();
if (drawable == null) return;
final RectF rect = getCenter(horizontal, vertical);
if (rect.left != 0 || rect.top != 0) {
postTranslate(rect.left, rect.top);
}
}
protected RectF getBitmapRect() {
final Drawable drawable = getDrawable();
if (drawable == null) return null;
final Matrix m = getImageViewMatrix();
mBitmapRect.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
m.mapRect(mBitmapRect);
return mBitmapRect;
}
protected RectF getCenter(final boolean horizontal, final boolean vertical) {
final Drawable drawable = getDrawable();
if (drawable == null) return new RectF(0, 0, 0, 0);
final RectF rect = getBitmapRect();
final float height = rect.height();
final float width = rect.width();
float deltaX = 0, deltaY = 0;
if (vertical) {
final int viewHeight = getHeight();
if (height < viewHeight) {
deltaY = (viewHeight - height) / 2 - rect.top;
} else if (rect.top > 0) {
deltaY = -rect.top;
} else if (rect.bottom < viewHeight) {
deltaY = getHeight() - rect.bottom;
}
}
if (horizontal) {
final int viewWidth = getWidth();
if (width < viewWidth) {
deltaX = (viewWidth - width) / 2 - rect.left;
} else if (rect.left > 0) {
deltaX = -rect.left;
} else if (rect.right < viewWidth) {
deltaX = viewWidth - rect.right;
}
}
mCenterRect.set(deltaX, deltaY, 0, 0);
return mCenterRect;
}
/**
* Setup the base matrix so that the image is centered and scaled properly.
*
* @param bitmap
* @param matrix
*/
protected void getProperBaseMatrix(final Drawable drawable, final Matrix matrix) {
final float viewWidth = getWidth();
final float viewHeight = getHeight();
final float w = drawable.getIntrinsicWidth();
final float h = drawable.getIntrinsicHeight();
matrix.reset();
if (w > viewWidth || h > viewHeight) {
final float widthScale = Math.min(viewWidth / w, 2.0f);
final float heightScale = Math.min(viewHeight / h, 2.0f);
final float scale = Math.min(widthScale, heightScale);
matrix.postScale(scale, scale);
final float tw = (viewWidth - w * scale) / 2.0f;
final float th = (viewHeight - h * scale) / 2.0f;
matrix.postTranslate(tw, th);
} else {
final float tw = (viewWidth - w) / 2.0f;
final float th = (viewHeight - h) / 2.0f;
matrix.postTranslate(tw, th);
}
}
/**
* Setup the base matrix so that the image is centered and scaled properly.
*
* @param bitmap
* @param matrix
*/
protected void getProperBaseMatrix2(final Drawable bitmap, final Matrix matrix) {
final float viewWidth = getWidth();
final float viewHeight = getHeight();
final float w = bitmap.getIntrinsicWidth();
final float h = bitmap.getIntrinsicHeight();
matrix.reset();
final float widthScale = Math.min(viewWidth / w, MAX_ZOOM);
final float heightScale = Math.min(viewHeight / h, MAX_ZOOM);
final float scale = Math.min(widthScale, heightScale);
matrix.postScale(scale, scale);
matrix.postTranslate((viewWidth - w * scale) / MAX_ZOOM, (viewHeight - h * scale) / MAX_ZOOM);
}
protected float getScale(final Matrix matrix) {
return getValue(matrix, Matrix.MSCALE_X);
}
protected float getValue(final Matrix matrix, final int whichValue) {
matrix.getValues(mMatrixValues);
return mMatrixValues[whichValue];
}
protected void init() {
setScaleType(ImageView.ScaleType.MATRIX);
}
protected float maxZoom() {
final Drawable drawable = getDrawable();
if (drawable == null) return 1F;
final float fw = (float) drawable.getIntrinsicWidth() / (float) mThisWidth;
final float fh = (float) drawable.getIntrinsicHeight() / (float) mThisHeight;
final float max = Math.max(fw, fh) * 4;
return max;
}
protected void onBitmapChanged(final Drawable bitmap) {
if (mListener != null) {
mListener.onBitmapChanged(bitmap);
}
}
@Override
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
super.onLayout(changed, left, top, right, bottom);
mThisWidth = right - left;
mThisHeight = bottom - top;
final Runnable r = mOnLayoutRunnable;
if (r != null) {
mOnLayoutRunnable = null;
r.run();
}
if (getDrawable() != null) {
if (mFitToScreen) {
getProperBaseMatrix2(getDrawable(), mBaseMatrix);
} else {
getProperBaseMatrix(getDrawable(), mBaseMatrix);
}
setImageMatrix(getImageViewMatrix());
}
}
protected void onZoom(final float scale) {
}
protected void panBy(final double dx, final double dy) {
final RectF rect = getBitmapRect();
mScrollRect.set((float) dx, (float) dy, 0, 0);
updateRect(rect, mScrollRect);
postTranslate(mScrollRect.left, mScrollRect.top);
center(true, true);
}
protected void postScale(final float scale, final float centerX, final float centerY) {
mSuppMatrix.postScale(scale, scale, centerX, centerY);
setImageMatrix(getImageViewMatrix());
}
protected void postTranslate(final float deltaX, final float deltaY) {
mSuppMatrix.postTranslate(deltaX, deltaY);
setImageMatrix(getImageViewMatrix());
}
protected void scrollBy(final float distanceX, final float distanceY, final double durationMs) {
final double dx = distanceX;
final double dy = distanceY;
final long startTime = System.currentTimeMillis();
mHandler.post(new Runnable() {
double old_x = 0;
double old_y = 0;
@Override
public void run() {
final long now = System.currentTimeMillis();
final double currentMs = Math.min(durationMs, now - startTime);
final double x = mEasing.easeOut(currentMs, 0, dx, durationMs);
final double y = mEasing.easeOut(currentMs, 0, dy, durationMs);
panBy(x - old_x, y - old_y);
old_x = x;
old_y = y;
if (currentMs < durationMs) {
mHandler.post(this);
} else {
final RectF centerRect = getCenter(true, true);
if (centerRect.left != 0 || centerRect.top != 0) {
scrollBy(centerRect.left, centerRect.top);
}
}
}
});
}
protected void updateRect(final RectF bitmapRect, final RectF scrollRect) {
if (bitmapRect == null || scrollRect == null) return;
final float width = getWidth();
final float height = getHeight();
if (bitmapRect.top >= 0 && bitmapRect.bottom <= height) {
scrollRect.top = 0;
}
if (bitmapRect.left >= 0 && bitmapRect.right <= width) {
scrollRect.left = 0;
}
if (bitmapRect.top + scrollRect.top >= 0 && bitmapRect.bottom > height) {
scrollRect.top = (int) (0 - bitmapRect.top);
}
if (bitmapRect.bottom + scrollRect.top <= height - 0 && bitmapRect.top < 0) {
scrollRect.top = (int) (height - 0 - bitmapRect.bottom);
}
if (bitmapRect.left + scrollRect.left >= 0) {
scrollRect.left = (int) (0 - bitmapRect.left);
}
if (bitmapRect.right + scrollRect.left <= width - 0) {
scrollRect.left = (int) (width - 0 - bitmapRect.right);
// Log.d( LOG_TAG, "scrollRect(2): " + scrollRect.toString() );
}
}
protected void zoomTo(final float scale) {
final float cx = getWidth() / 2F;
final float cy = getHeight() / 2F;
zoomTo(scale, cx, cy);
}
protected void zoomTo(float scale, final float centerX, final float centerY) {
// Log.i(LOG_TAG, "zoomTo");
if (scale > mMaxZoom) {
scale = mMaxZoom;
}
final float oldScale = getScale();
final float deltaScale = scale / oldScale;
postScale(deltaScale, centerX, centerY);
onZoom(getScale());
center(true, true);
}
protected void zoomTo(final float scale, final float centerX, final float centerY, final float durationMs) {
// Log.i( LOG_TAG, "zoomTo: " + scale + ", " + centerX + ": " + centerY
// );
final long startTime = System.currentTimeMillis();
final float incrementPerMs = (scale - getScale()) / durationMs;
final float oldScale = getScale();
mHandler.post(new Runnable() {
@Override
public void run() {
final long now = System.currentTimeMillis();
final float currentMs = Math.min(durationMs, now - startTime);
final float target = oldScale + incrementPerMs * currentMs;
zoomTo(target, centerX, centerY);
if (currentMs < durationMs) {
mHandler.post(this);
} else {
// if ( getScale() < 1f ) {}
}
}
});
}
public interface OnBitmapChangedListener {
void onBitmapChanged(Drawable drawable);
}
}

View File

@ -0,0 +1,487 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.sephiroth.android.library.imagezoom;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v4.view.MotionEventCompat;
import android.util.DisplayMetrics;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
/**
* Detects transformation gestures involving more than one pointer
* ("multitouch") using the supplied {@link MotionEvent}s. The
* {@link OnScaleGestureListener} callback will notify users when a particular
* gesture event has occurred. This class should only be used with
* {@link MotionEvent}s reported via touch. To use this class:
* <ul>
* <li>Create an instance of the {@code ScaleGestureDetector} for your
* {@link View}
* <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
* {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback will
* be executed when the events occur.
* </ul>
*/
@TargetApi(Build.VERSION_CODES.ECLAIR)
public class ScaleGestureDetector {
/**
* This value is the threshold ratio between our previous combined pressure
* and the current combined pressure. We will only fire an onScale event if
* the computed ratio between the current and previous event pressures is
* greater than this value. When pressure decreases rapidly between events
* the position values can often be imprecise, as it usually indicates that
* the user is in the process of lifting a pointer off of the device. Its
* value was tuned experimentally.
*/
private static final float PRESSURE_THRESHOLD = 0.67f;
private final Context mContext;
private final OnScaleGestureListener mListener;
private boolean mGestureInProgress;
private MotionEvent mPrevEvent;
private MotionEvent mCurrEvent;
private float mFocusX;
private float mFocusY;
private float mPrevFingerDiffX;
private float mPrevFingerDiffY;
private float mCurrFingerDiffX;
private float mCurrFingerDiffY;
private float mCurrLen;
private float mPrevLen;
private float mScaleFactor;
private float mCurrPressure;
private float mPrevPressure;
private long mTimeDelta;
private final float mEdgeSlop;
private float mRightSlopEdge;
private float mBottomSlopEdge;
private boolean mSloppyGesture;
public ScaleGestureDetector(final Context context, final OnScaleGestureListener listener) {
final ViewConfiguration config = ViewConfiguration.get(context);
mContext = context;
mListener = listener;
mEdgeSlop = config.getScaledEdgeSlop();
}
/**
* Return the current distance between the two pointers forming the gesture
* in progress.
*
* @return Distance between pointers in pixels.
*/
public float getCurrentSpan() {
if (mCurrLen == -1) {
final float cvx = mCurrFingerDiffX;
final float cvy = mCurrFingerDiffY;
mCurrLen = FloatMath.sqrt(cvx * cvx + cvy * cvy);
}
return mCurrLen;
}
/**
* Return the event time of the current event being processed.
*
* @return Current event time in milliseconds.
*/
public long getEventTime() {
return mCurrEvent.getEventTime();
}
/**
* Get the X coordinate of the current gesture's focal point. If a gesture
* is in progress, the focal point is directly between the two pointers
* forming the gesture. If a gesture is ending, the focal point is the
* location of the remaining pointer on the screen. If
* {@link #isInProgress()} would return false, the result of this function
* is undefined.
*
* @return X coordinate of the focal point in pixels.
*/
public float getFocusX() {
return mFocusX;
}
/**
* Get the Y coordinate of the current gesture's focal point. If a gesture
* is in progress, the focal point is directly between the two pointers
* forming the gesture. If a gesture is ending, the focal point is the
* location of the remaining pointer on the screen. If
* {@link #isInProgress()} would return false, the result of this function
* is undefined.
*
* @return Y coordinate of the focal point in pixels.
*/
public float getFocusY() {
return mFocusY;
}
/**
* Return the previous distance between the two pointers forming the gesture
* in progress.
*
* @return Previous distance between pointers in pixels.
*/
public float getPreviousSpan() {
if (mPrevLen == -1) {
final float pvx = mPrevFingerDiffX;
final float pvy = mPrevFingerDiffY;
mPrevLen = FloatMath.sqrt(pvx * pvx + pvy * pvy);
}
return mPrevLen;
}
/**
* Return the scaling factor from the previous scale event to the current
* event. This value is defined as ({@link #getCurrentSpan()} /
* {@link #getPreviousSpan()}).
*
* @return The current scaling factor.
*/
public float getScaleFactor() {
if (mScaleFactor == -1) {
mScaleFactor = getCurrentSpan() / getPreviousSpan();
}
return mScaleFactor;
}
/**
* Return the time difference in milliseconds between the previous accepted
* scaling event and the current scaling event.
*
* @return Time difference since the last scaling event in milliseconds.
*/
public long getTimeDelta() {
return mTimeDelta;
}
/**
* Returns {@code true} if a two-finger scale gesture is in progress.
*
* @return {@code true} if a scale gesture is in progress, {@code false}
* otherwise.
*/
public boolean isInProgress() {
return mGestureInProgress;
}
public boolean onTouchEvent(final MotionEvent event) {
final int action = event.getAction();
final boolean handled = true;
if (!mGestureInProgress) {
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN: {
// We have a new multi-finger gesture
// as orientation can change, query the metrics in touch
// down
final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
// Be paranoid in case we missed an event
reset();
mPrevEvent = MotionEvent.obtain(event);
mTimeDelta = 0;
setContext(event);
// Check if we have a sloppy gesture. If so, delay
// the beginning of the gesture until we're sure that's
// what the user wanted. Sloppy gestures can happen if the
// edge of the user's hand is touching the screen, for
// example.
final float edgeSlop = mEdgeSlop;
final float rightSlop = mRightSlopEdge;
final float bottomSlop = mBottomSlopEdge;
final float x0 = event.getRawX();
final float y0 = event.getRawY();
final float x1 = getRawX(event, 1);
final float y1 = getRawY(event, 1);
final boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop || x0 > rightSlop || y0 > bottomSlop;
final boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > rightSlop || y1 > bottomSlop;
if (p0sloppy && p1sloppy) {
mFocusX = -1;
mFocusY = -1;
mSloppyGesture = true;
} else if (p0sloppy) {
mFocusX = MotionEventCompat.getX(event, 1);
mFocusY = MotionEventCompat.getY(event, 1);
mSloppyGesture = true;
} else if (p1sloppy) {
mFocusX = MotionEventCompat.getX(event, 0);
mFocusY = MotionEventCompat.getY(event, 0);
mSloppyGesture = true;
} else {
mGestureInProgress = mListener.onScaleBegin(this);
}
}
break;
case MotionEvent.ACTION_MOVE:
if (mSloppyGesture) {
// Initiate sloppy gestures if we've moved outside of
// the slop area.
final float edgeSlop = mEdgeSlop;
final float rightSlop = mRightSlopEdge;
final float bottomSlop = mBottomSlopEdge;
final float x0 = event.getRawX();
final float y0 = event.getRawY();
final float x1 = getRawX(event, 1);
final float y1 = getRawY(event, 1);
final boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop || x0 > rightSlop || y0 > bottomSlop;
final boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > rightSlop || y1 > bottomSlop;
if (p0sloppy && p1sloppy) {
mFocusX = -1;
mFocusY = -1;
} else if (p0sloppy) {
mFocusX = MotionEventCompat.getX(event, 1);
mFocusY = MotionEventCompat.getY(event, 1);
} else if (p1sloppy) {
mFocusX = MotionEventCompat.getX(event, 0);
mFocusY = MotionEventCompat.getY(event, 0);
} else {
mSloppyGesture = false;
mGestureInProgress = mListener.onScaleBegin(this);
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (mSloppyGesture) {
// Set focus point to the remaining finger
final int id = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT == 0 ? 1
: 0;
mFocusX = event.getX(id);
mFocusY = event.getY(id);
}
break;
}
} else {
// Transform gesture in progress - attempt to handle it
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_UP:
// Gesture ended
setContext(event);
// Set focus point to the remaining finger
final int id = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT == 0 ? 1
: 0;
mFocusX = event.getX(id);
mFocusY = event.getY(id);
if (!mSloppyGesture) {
mListener.onScaleEnd(this);
}
reset();
break;
case MotionEvent.ACTION_CANCEL:
if (!mSloppyGesture) {
mListener.onScaleEnd(this);
}
reset();
break;
case MotionEvent.ACTION_MOVE:
setContext(event);
// Only accept the event if our relative pressure is within
// a certain limit - this can help filter shaky data as a
// finger is lifted.
if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
final boolean updatePrevious = mListener.onScale(this);
if (updatePrevious) {
mPrevEvent.recycle();
mPrevEvent = MotionEvent.obtain(event);
}
}
break;
}
}
return handled;
}
private void reset() {
if (mPrevEvent != null) {
mPrevEvent.recycle();
mPrevEvent = null;
}
if (mCurrEvent != null) {
mCurrEvent.recycle();
mCurrEvent = null;
}
mSloppyGesture = false;
mGestureInProgress = false;
}
private void setContext(final MotionEvent curr) {
if (mCurrEvent != null) {
mCurrEvent.recycle();
}
mCurrEvent = MotionEvent.obtain(curr);
mCurrLen = -1;
mPrevLen = -1;
mScaleFactor = -1;
final MotionEvent prev = mPrevEvent;
final float px0 = prev.getX(0);
final float py0 = prev.getY(0);
final float px1 = prev.getX(1);
final float py1 = prev.getY(1);
final float cx0 = curr.getX(0);
final float cy0 = curr.getY(0);
final float cx1 = curr.getX(1);
final float cy1 = curr.getY(1);
final float pvx = px1 - px0;
final float pvy = py1 - py0;
final float cvx = cx1 - cx0;
final float cvy = cy1 - cy0;
mPrevFingerDiffX = pvx;
mPrevFingerDiffY = pvy;
mCurrFingerDiffX = cvx;
mCurrFingerDiffY = cvy;
mFocusX = cx0 + cvx * 0.5f;
mFocusY = cy0 + cvy * 0.5f;
mTimeDelta = curr.getEventTime() - prev.getEventTime();
mCurrPressure = curr.getPressure(0) + curr.getPressure(1);
mPrevPressure = prev.getPressure(0) + prev.getPressure(1);
}
/**
* MotionEvent has no getRawX(int) method; simulate it pending future API
* approval.
*/
private static float getRawX(final MotionEvent event, final int pointerIndex) {
final float offset = event.getX() - event.getRawX();
return event.getX(pointerIndex) + offset;
}
/**
* MotionEvent has no getRawY(int) method; simulate it pending future API
* approval.
*/
private static float getRawY(final MotionEvent event, final int pointerIndex) {
final float offset = event.getY() - event.getRawY();
return event.getY(pointerIndex) + offset;
}
/**
* The listener for receiving notifications when gestures occur. If you want
* to listen for all the different gestures then implement this interface.
* If you only want to listen for a subset it might be easier to extend
* {@link SimpleOnScaleGestureListener}. An application will receive events
* in the following order:
* <ul>
* <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
* <li>Zero or more
* {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
* <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
* </ul>
*/
public static interface OnScaleGestureListener {
/**
* Responds to scaling events for a gesture in progress. Reported by
* pointer motion.
*
* @param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* @return Whether or not the detector should consider this event as
* handled. If an event was not handled, the detector will
* continue to accumulate movement until an event is handled.
* This can be useful if an application, for example, only wants
* to update scaling factors if the change is greater than 0.01.
*/
public boolean onScale(ScaleGestureDetector detector);
/**
* Responds to the beginning of a scaling gesture. Reported by new
* pointers going down.
*
* @param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* @return Whether or not the detector should continue recognizing this
* gesture. For example, if a gesture is beginning with a focal
* point outside of a region where it makes sense,
* onScaleBegin() may return false to ignore the rest of the
* gesture.
*/
public boolean onScaleBegin(ScaleGestureDetector detector);
/**
* Responds to the end of a scale gesture. Reported by existing pointers
* going up. Once a scale has ended,
* {@link ScaleGestureDetector#getFocusX()} and
* {@link ScaleGestureDetector#getFocusY()} will return the location of
* the pointer remaining on the screen.
*
* @param detector The detector reporting the event - use this to
* retrieve extended info about event state.
*/
public void onScaleEnd(ScaleGestureDetector detector);
}
/**
* A convenience class to extend when you only want to listen for a subset
* of scaling-related events. This implements all methods in
* {@link OnScaleGestureListener} but does nothing.
* {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
* {@code false} so that a subclass can retrieve the accumulated scale
* factor in an overridden onScaleEnd.
* {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
* {@code true}.
*/
public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
@Override
public boolean onScale(final ScaleGestureDetector detector) {
return false;
}
@Override
public boolean onScaleBegin(final ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(final ScaleGestureDetector detector) {
// Intentionally empty
}
}
}

View File

@ -0,0 +1,20 @@
package it.sephiroth.android.library.imagezoom.easing;
public class Cubic implements Easing {
@Override
public double easeIn(double time, final double start, final double end, final double duration) {
return end * (time /= duration) * time * time + start;
}
@Override
public double easeInOut(double time, final double start, final double end, final double duration) {
if ((time /= duration / 2.0) < 1.0) return end / 2.0 * time * time * time + start;
return end / 2.0 * ((time -= 2.0) * time * time + 2.0) + start;
}
@Override
public double easeOut(double time, final double start, final double end, final double duration) {
return end * ((time = time / duration - 1.0) * time * time + 1.0) + start;
}
}

View File

@ -0,0 +1,10 @@
package it.sephiroth.android.library.imagezoom.easing;
public interface Easing {
double easeIn(double time, double start, double end, double duration);
double easeInOut(double time, double start, double end, double duration);
double easeOut(double time, double start, double end, double duration);
}

View File

@ -0,0 +1,85 @@
package it.sephiroth.android.library.imagezoom.graphics;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import java.io.InputStream;
/**
* Fast bitmap drawable. Does not support states. it only support alpha and
* colormatrix
*
* @author alessandro
*/
public class FastBitmapDrawable extends Drawable implements IBitmapDrawable {
protected Bitmap mBitmap;
protected Paint mPaint;
public FastBitmapDrawable(final Bitmap b) {
mBitmap = b;
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setFilterBitmap(true);
}
public FastBitmapDrawable(final Resources res, final InputStream is) {
this(BitmapFactory.decodeStream(is));
}
@Override
public void draw(final Canvas canvas) {
canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
}
@Override
public Bitmap getBitmap() {
return mBitmap;
}
@Override
public int getIntrinsicHeight() {
return mBitmap.getHeight();
}
@Override
public int getIntrinsicWidth() {
return mBitmap.getWidth();
}
@Override
public int getMinimumHeight() {
return mBitmap.getHeight();
}
@Override
public int getMinimumWidth() {
return mBitmap.getWidth();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(final int alpha) {
mPaint.setAlpha(alpha);
}
public void setAntiAlias(final boolean value) {
mPaint.setAntiAlias(value);
invalidateSelf();
}
@Override
public void setColorFilter(final ColorFilter cf) {
mPaint.setColorFilter(cf);
}
}

View File

@ -0,0 +1,15 @@
package it.sephiroth.android.library.imagezoom.graphics;
import android.graphics.Bitmap;
import it.sephiroth.android.library.imagezoom.ImageViewTouchBase;
/**
* Base interface used in the {@link ImageViewTouchBase} view
*
* @author alessandro
*/
public interface IBitmapDrawable {
Bitmap getBitmap();
}

View File

@ -0,0 +1,6 @@
package it.sephiroth.android.library.imagezoom.utils;
public interface IDisposable {
void dispose();
}

View File

@ -0,0 +1,621 @@
package me.imid.swipebacklayout.lib;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.util.MathUtils;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.accessor.ViewAccessor;
public class SwipeBackLayout extends FrameLayout {
/**
* Minimum velocity that will be detected as a fling
*/
private static final int MIN_FLING_VELOCITY = 400; // dips per second
private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
/**
* Edge flag indicating that the left edge should be affected.
*/
public static final int EDGE_LEFT = ViewDragHelper.EDGE_LEFT;
/**
* Edge flag indicating that the right edge should be affected.
*/
public static final int EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT;
/**
* Edge flag indicating that the bottom edge should be affected.
*/
public static final int EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM;
/**
* Edge flag set indicating all edges should be affected.
*/
public static final int EDGE_ALL = EDGE_LEFT | EDGE_RIGHT | EDGE_BOTTOM;
/**
* A view is not currently being dragged or animating as a result of a
* fling/snap.
*/
public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
/**
* A view is currently being dragged. The position is currently changing as
* a result of user input or simulated user input.
*/
public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
/**
* A view is currently settling into place as a result of a fling or
* predefined non-interactive motion.
*/
public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
/**
* Default threshold of scroll
*/
private static final float DEFAULT_SCROLL_THRESHOLD = 0.3f;
private static final int OVERSCROLL_DISTANCE = 10;
private static final int[] EDGE_FLAGS = { EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL };
private int mEdgeFlags;
/**
* Threshold of scroll, we will close the activity, when scrollPercent over
* this value;
*/
private float mScrollThreshold = DEFAULT_SCROLL_THRESHOLD;
private Activity mActivity;
private boolean mEnable = true;
private View mContentView;
private ImageView mBackgroundView;
private final ViewDragHelper mDragHelper;
private float mScrollPercent;
private int mContentLeft;
private int mContentTop;
private SwipeListener mSwipeListener;
private Drawable mShadowLeft;
private Drawable mShadowRight;
private Drawable mShadowBottom;
private float mScrimOpacity;
private int mScrimColor;
private float mScrimAlpha;
private boolean mInLayout;
private final Rect mTmpRect = new Rect();
/**
* Edge being dragged
*/
private int mTrackingEdge;
private float mScalePercent;
private OnSwipeBackScrollListener mOnSwipeBackScrollListener;
public SwipeBackLayout(final Context context) {
this(context, null);
}
public SwipeBackLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.SwipeBackLayoutStyle);
}
public SwipeBackLayout(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs);
setFitsSystemWindows(true);
mDragHelper = ViewDragHelper.create(this, new ViewDragCallback());
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout, defStyle,
R.style.SwipeBackLayout);
final int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edgeSize, -1);
if (edgeSize > 0) {
setEdgeSize(edgeSize);
}
final int mode = EDGE_FLAGS[a.getInt(R.styleable.SwipeBackLayout_edgeFlag, 0)];
setEdgeTrackingEnabled(mode);
final int shadowLeft = a.getResourceId(R.styleable.SwipeBackLayout_shadowLeft, R.drawable.shadow_left);
final int shadowRight = a.getResourceId(R.styleable.SwipeBackLayout_shadowRight, R.drawable.shadow_right);
final int shadowBottom = a.getResourceId(R.styleable.SwipeBackLayout_shadowBottom, R.drawable.shadow_bottom);
final int scrimColor = a.getColor(R.styleable.SwipeBackLayout_scrimColor, DEFAULT_SCRIM_COLOR);
final float scrimAlpha = a.getFloat(R.styleable.SwipeBackLayout_scrimAlpha, Color.alpha(scrimColor) / 255.0f);
final float scalePercent = a.getFraction(R.styleable.SwipeBackLayout_scalePercent, 1, 1, 1);
setShadow(shadowLeft, EDGE_LEFT);
setShadow(shadowRight, EDGE_RIGHT);
setShadow(shadowBottom, EDGE_BOTTOM);
setScalePercent(scalePercent);
setScrimColor(scrimColor);
setScrimAlpha(scrimAlpha);
a.recycle();
final float density = getResources().getDisplayMetrics().density;
final float minVel = MIN_FLING_VELOCITY * density;
mDragHelper.setMinVelocity(minVel);
}
public void attachToActivity(final Activity activity) {
mActivity = activity;
final Drawable background = ThemeUtils.getWindowBackground(activity);
final ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
final ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
final ImageView backgroundChild = new ImageView(activity);
backgroundChild.setScaleType(ScaleType.CENTER_CROP);
ViewAccessor.setBackground(decorChild, background);
decor.removeView(decorChild);
setBackgroundView(backgroundChild);
setContentView(decorChild);
addView(decorChild, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
final FrameLayout frame = new FrameLayout(activity);
frame.setFitsSystemWindows(true);
frame.addView(backgroundChild, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
frame.addView(this);
decor.addView(frame);
}
@Override
public void computeScroll() {
mScrimOpacity = 1 - mScrollPercent;
updateWindowBackground();
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
if (mOnSwipeBackScrollListener != null) {
mOnSwipeBackScrollListener.onSwipeBackScroll(mScrollPercent);
}
}
public int getEdgeFlags() {
return mEdgeFlags;
}
public Drawable getShadow(final int edgeFlag) {
if ((edgeFlag & EDGE_LEFT) != 0)
return mShadowLeft;
else if ((edgeFlag & EDGE_RIGHT) != 0)
return mShadowRight;
else if ((edgeFlag & EDGE_BOTTOM) != 0) return mShadowBottom;
return null;
}
public int getTrackingEdge() {
return mTrackingEdge;
}
public boolean isSwiping() {
return mDragHelper != null && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE;
}
@Override
public boolean onInterceptTouchEvent(final MotionEvent event) {
if (!mEnable) return false;
try {
return mDragHelper.shouldInterceptTouchEvent(event);
} catch (final ArrayIndexOutOfBoundsException e) {
// FIXME: handle exception
// issues #9
return false;
}
}
@Override
public boolean onTouchEvent(final MotionEvent event) {
if (!mEnable) return false;
mDragHelper.processTouchEvent(event);
return true;
}
@Override
public void requestLayout() {
if (!mInLayout) {
super.requestLayout();
}
}
/**
* Scroll out contentView and finish the activity
*/
public void scrollToFinishActivity() {
final int childWidth = mContentView.getWidth();
final int childHeight = mContentView.getHeight();
int left = 0, top = 0;
if ((mEdgeFlags & EDGE_LEFT) != 0) {
left = childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE;
mTrackingEdge = EDGE_LEFT;
} else if ((mEdgeFlags & EDGE_RIGHT) != 0) {
left = -childWidth - mShadowRight.getIntrinsicWidth() - OVERSCROLL_DISTANCE;
mTrackingEdge = EDGE_RIGHT;
} else if ((mEdgeFlags & EDGE_BOTTOM) != 0) {
top = -childHeight - mShadowBottom.getIntrinsicHeight() - OVERSCROLL_DISTANCE;
mTrackingEdge = EDGE_BOTTOM;
}
mDragHelper.smoothSlideViewTo(mContentView, left, top);
invalidate();
}
/**
* Set the size of an edge. This is the range in pixels along the edges of
* this view that will actively detect edge touches or drags if edge
* tracking is enabled.
*
* @param size The size of an edge in pixels
*/
public void setEdgeSize(final int size) {
mDragHelper.setEdgeSize(size);
}
/**
* Enable edge tracking for the selected edges of the parent view. The
* callback's
* {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeTouched(int, int)}
* and
* {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeDragStarted(int, int)}
* methods will only be invoked for edges for which edge tracking has been
* enabled.
*
* @param edgeFlags Combination of edge flags describing the edges to watch
* @see #EDGE_LEFT
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void setEdgeTrackingEnabled(final int edgeFlags) {
mEdgeFlags = edgeFlags;
mDragHelper.setEdgeTrackingEnabled(mEdgeFlags);
}
public void setEnableGesture(final boolean enable) {
mEnable = enable;
}
public void setOnSwipeBackScrollListener(final OnSwipeBackScrollListener listener) {
mOnSwipeBackScrollListener = listener;
}
/**
* Set a color to use for the scrim that obscures primary content while a
* drawer is open.
*
* @param color Color to use in 0xAARRGGBB format.
*/
public void setScrimColor(final int color) {
mScrimColor = color;
invalidate();
}
/**
* Set scroll threshold, we will close the activity, when scrollPercent over
* this value
*
* @param threshold
*/
public void setScrollThresHold(final float threshold) {
if (threshold >= 1.0f || threshold <= 0)
throw new IllegalArgumentException("Threshold value should be between 0 and 1.0");
mScrollThreshold = threshold;
}
/**
* Set a drawable used for edge shadow.
*
* @param shadow Drawable to use
* @param edgeFlags Combination of edge flags describing the edge to set
* @see #EDGE_LEFT
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void setShadow(final Drawable shadow, final int edgeFlag) {
if ((edgeFlag & EDGE_LEFT) != 0) {
mShadowLeft = shadow;
} else if ((edgeFlag & EDGE_RIGHT) != 0) {
mShadowRight = shadow;
} else if ((edgeFlag & EDGE_BOTTOM) != 0) {
mShadowBottom = shadow;
}
invalidate();
}
/**
* Set a drawable used for edge shadow.
*
* @param resId Resource of drawable to use
* @param edgeFlags Combination of edge flags describing the edge to set
* @see #EDGE_LEFT
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void setShadow(final int resId, final int edgeFlag) {
setShadow(getResources().getDrawable(resId), edgeFlag);
}
/**
* Register a callback to be invoked when a swipe event is sent to this
* view.
*
* @param listener the swipe listener to attach to this view
*/
public void setSwipeListener(final SwipeListener listener) {
mSwipeListener = listener;
}
public void setWindowBackgroundDrawable(final Drawable d) {
if (mBackgroundView == null) return;
mBackgroundView.setImageDrawable(d);
}
@Override
protected boolean drawChild(final Canvas canvas, final View child, final long drawingTime) {
final boolean drawContent = child == mContentView;
drawShadow(canvas, child);
final boolean ret = super.drawChild(canvas, child, drawingTime);
if (mScrimOpacity > 0 && drawContent && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
drawScrim(canvas, child);
}
return ret;
}
@Override
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
mInLayout = true;
if (mContentView != null) {
mContentView.layout(mContentLeft, mContentTop, mContentLeft + mContentView.getMeasuredWidth(), mContentTop
+ mContentView.getMeasuredHeight());
}
mInLayout = false;
}
private void drawScrim(final Canvas canvas, final View child) {
final int alpha = (int) (mScrimAlpha * 255 * mScrimOpacity);
final int color = alpha << 24 | mScrimColor & 0xffffff;
if ((mTrackingEdge & EDGE_LEFT) != 0) {
canvas.clipRect(0, 0, child.getLeft(), getHeight());
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
canvas.clipRect(child.getRight(), 0, getRight(), getHeight());
} else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
canvas.clipRect(child.getLeft(), child.getBottom(), getRight(), getHeight());
}
canvas.drawColor(color);
}
private void drawShadow(final Canvas canvas, final View child) {
final Rect childRect = mTmpRect;
child.getHitRect(childRect);
if ((mEdgeFlags & EDGE_LEFT) != 0) {
mShadowLeft.setBounds(childRect.left - mShadowLeft.getIntrinsicWidth(), childRect.top, childRect.left,
childRect.bottom);
mShadowLeft.draw(canvas);
}
if ((mEdgeFlags & EDGE_RIGHT) != 0) {
mShadowRight.setBounds(childRect.right, childRect.top, childRect.right + mShadowRight.getIntrinsicWidth(),
childRect.bottom);
mShadowRight.draw(canvas);
}
if ((mEdgeFlags & EDGE_BOTTOM) != 0) {
mShadowBottom.setBounds(childRect.left, childRect.bottom, childRect.right,
childRect.bottom + mShadowBottom.getIntrinsicHeight());
mShadowBottom.draw(canvas);
}
}
private void setBackgroundView(final ImageView view) {
mBackgroundView = view;
}
/**
* Set up contentView which will be moved by user gesture
*
* @param view
*/
private void setContentView(final View view) {
mContentView = view;
}
private void setScalePercent(final float scalePercent) {
mScalePercent = scalePercent;
}
private void setScrimAlpha(final float scrimAlpha) {
mScrimAlpha = scrimAlpha;
invalidate();
}
private void updateWindowBackground() {
if (mBackgroundView == null) return;
final float scrollPercentAbs = Math.abs(mScrollPercent);
final float percent = MathUtils.clamp(1 - (1 - scrollPercentAbs) * (1 - mScalePercent), 1, 0);
mBackgroundView.setScaleType(ScaleType.CENTER_CROP);
mBackgroundView.setScaleX(percent);
mBackgroundView.setScaleY(percent);
mBackgroundView.setVisibility(mScrollPercent <= 0 ? View.INVISIBLE : View.VISIBLE);
mBackgroundView.setAlpha(scrollPercentAbs);
// mBackgroundView.setScrollPercent(mScrollPercent / mScalePercent);
}
public interface OnSwipeBackScrollListener {
void onSwipeBackScroll(float percent);
}
public static interface SwipeListener {
/**
* Invoke when edge touched
*
* @param edgeFlag edge flag describing the edge being touched
* @see #EDGE_LEFT
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void onEdgeTouch(int edgeFlag);
/**
* Invoke when scroll percent over the threshold for the first time
*/
public void onScrollOverThreshold();
/**
* Invoke when state change
*
* @param state flag to describe scroll state
* @see #STATE_IDLE
* @see #STATE_DRAGGING
* @see #STATE_SETTLING
* @param scrollPercent scroll percent of this view
*/
public void onScrollStateChange(int state, float scrollPercent);
}
private class ViewDragCallback extends ViewDragHelper.Callback {
private boolean mIsScrollOverValid;
@Override
public int clampViewPositionHorizontal(final View child, final int left, final int dx) {
int ret = 0;
if ((mTrackingEdge & EDGE_LEFT) != 0) {
ret = Math.min(child.getWidth(), Math.max(left, 0));
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
ret = Math.min(0, Math.max(left, -child.getWidth()));
}
return ret;
}
@Override
public int clampViewPositionVertical(final View child, final int top, final int dy) {
int ret = 0;
if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
ret = Math.min(0, Math.max(top, -child.getHeight()));
}
return ret;
}
@Override
public int getViewHorizontalDragRange(final View child) {
return mEdgeFlags & (EDGE_LEFT | EDGE_RIGHT);
}
@Override
public int getViewVerticalDragRange(final View child) {
return mEdgeFlags & EDGE_BOTTOM;
}
@Override
public void onViewDragStateChanged(final int state) {
super.onViewDragStateChanged(state);
if (mSwipeListener != null) {
mSwipeListener.onScrollStateChange(state, mScrollPercent);
}
}
@Override
public void onViewPositionChanged(final View changedView, final int left, final int top, final int dx,
final int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if ((mTrackingEdge & EDGE_LEFT) != 0) {
mScrollPercent = Math.abs((float) left / (mContentView.getWidth() + mShadowLeft.getIntrinsicWidth()));
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
mScrollPercent = Math.abs((float) left / (mContentView.getWidth() + mShadowRight.getIntrinsicWidth()));
} else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
mScrollPercent = Math
.abs((float) top / (mContentView.getHeight() + mShadowBottom.getIntrinsicHeight()));
}
mContentLeft = left;
mContentTop = top;
invalidate();
if (mScrollPercent < mScrollThreshold && !mIsScrollOverValid) {
mIsScrollOverValid = true;
}
if (mSwipeListener != null && mDragHelper.getViewDragState() == STATE_DRAGGING
&& mScrollPercent >= mScrollThreshold && mIsScrollOverValid) {
mIsScrollOverValid = false;
mSwipeListener.onScrollOverThreshold();
}
if (mScrollPercent >= 1) {
if (!mActivity.isFinishing()) {
mActivity.finish();
mActivity.overridePendingTransition(0, 0);
}
}
}
@Override
public void onViewReleased(final View releasedChild, final float xvel, final float yvel) {
final int childWidth = releasedChild.getWidth();
final int childHeight = releasedChild.getHeight();
int left = 0, top = 0;
if ((mTrackingEdge & EDGE_LEFT) != 0) {
left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth
+ mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0;
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
left = xvel < 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? -(childWidth
+ mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE) : 0;
} else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
top = yvel < 0 || yvel == 0 && mScrollPercent > mScrollThreshold ? -(childHeight
+ mShadowBottom.getIntrinsicHeight() + OVERSCROLL_DISTANCE) : 0;
}
mDragHelper.settleCapturedViewAt(left, top);
invalidate();
}
@Override
public boolean tryCaptureView(final View view, final int i) {
final boolean ret = mDragHelper.isEdgeTouched(mEdgeFlags, i);
if (ret) {
if (mDragHelper.isEdgeTouched(EDGE_LEFT, i)) {
mTrackingEdge = EDGE_LEFT;
} else if (mDragHelper.isEdgeTouched(EDGE_RIGHT, i)) {
mTrackingEdge = EDGE_RIGHT;
} else if (mDragHelper.isEdgeTouched(EDGE_BOTTOM, i)) {
mTrackingEdge = EDGE_BOTTOM;
}
if (mSwipeListener != null) {
mSwipeListener.onEdgeTouch(mTrackingEdge);
}
mIsScrollOverValid = true;
}
return ret;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
package me.imid.swipebacklayout.lib.app;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import me.imid.swipebacklayout.lib.SwipeBackLayout;
@SuppressLint("Registered")
public class SwipeBackActivity extends FragmentActivity implements SwipeBackActivityBase {
private SwipeBackActivityHelper mSwipebackHelper;
@Override
public View findViewById(final int id) {
final View v = super.findViewById(id);
if (v == null && mSwipebackHelper != null) return mSwipebackHelper.findViewById(id);
return v;
}
@Override
public SwipeBackLayout getSwipeBackLayout() {
return mSwipebackHelper.getSwipeBackLayout();
}
@Override
public void scrollToFinishActivity() {
getSwipeBackLayout().scrollToFinishActivity();
}
@Override
public void setSwipeBackEnable(final boolean enable) {
getSwipeBackLayout().setEnableGesture(enable);
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSwipebackHelper = new SwipeBackActivityHelper(this);
mSwipebackHelper.onActivtyCreate();
}
@Override
protected void onDestroy() {
super.onDestroy();
mSwipebackHelper.onDestroy();
}
@Override
protected void onPostCreate(final Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mSwipebackHelper.onPostCreate();
}
}

View File

@ -0,0 +1,21 @@
package me.imid.swipebacklayout.lib.app;
import me.imid.swipebacklayout.lib.SwipeBackLayout;
/**
* @author Yrom
*/
public interface SwipeBackActivityBase {
/**
* @return the SwipeBackLayout associated with this activity.
*/
public abstract SwipeBackLayout getSwipeBackLayout();
/**
* Scroll out contentView and finish the activity
*/
public abstract void scrollToFinishActivity();
public abstract void setSwipeBackEnable(boolean enable);
}

View File

@ -0,0 +1,67 @@
package me.imid.swipebacklayout.lib.app;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import me.imid.swipebacklayout.lib.SwipeBackLayout;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.graphic.EmptyDrawable;
import org.mariotaku.twidere.util.SwipebackActivityUtils.SwipebackScreenshotManager;
/**
* @author Yrom
*
*/
public class SwipeBackActivityHelper implements TwidereConstants {
private final Activity mActivity;
private SwipeBackLayout mSwipeBackLayout;
public SwipeBackActivityHelper(final Activity activity) {
mActivity = activity;
}
public View findViewById(final int id) {
if (mSwipeBackLayout != null) return mSwipeBackLayout.findViewById(id);
return null;
}
public SwipeBackLayout getSwipeBackLayout() {
return mSwipeBackLayout;
}
public void onActivtyCreate() {
final Window w = mActivity.getWindow();
w.setBackgroundDrawable(new EmptyDrawable());
mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate(R.layout.swipeback_layout, null);
}
public void onDestroy() {
if (mActivity.isFinishing()) {
final Intent intent = mActivity.getIntent();
final TwidereApplication app = TwidereApplication.getInstance(mActivity);
final SwipebackScreenshotManager sm = app.getSwipebackScreenshotManager();
sm.remove(intent.getLongExtra(EXTRA_ACTIVITY_SCREENSHOT_ID, -1));
}
}
public void onPostCreate() {
mSwipeBackLayout.attachToActivity(mActivity);
final Intent intent = mActivity.getIntent();
final TwidereApplication app = TwidereApplication.getInstance(mActivity);
final SwipebackScreenshotManager sm = app.getSwipebackScreenshotManager();
final Bitmap b = sm.get(intent.getLongExtra(EXTRA_ACTIVITY_SCREENSHOT_ID, -1));
if (b != null) {
mSwipeBackLayout.setWindowBackgroundDrawable(new BitmapDrawable(mActivity.getResources(), b));
}
mSwipeBackLayout.setEnableGesture(b != null);
}
}

View File

@ -0,0 +1,213 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3;
import org.apache.commons.lang3.text.translate.AggregateTranslator;
import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
import org.apache.commons.lang3.text.translate.EntityArrays;
import org.apache.commons.lang3.text.translate.LookupTranslator;
import org.apache.commons.lang3.text.translate.NumericEntityUnescaper;
/**
* <p>
* Escapes and unescapes {@code String}s for Java, Java Script, HTML and XML.
* </p>
* <p>
* #ThreadSafe#
* </p>
*
* @since 2.0
* @version $Id: StringEscapeUtils.java 1148520 2011-07-19 20:53:23Z ggregory $
*/
public class StringEscapeUtils {
/* ESCAPE TRANSLATORS */
/**
* Translator object for escaping HTML version 3.0. While
* {@link #escapeHtml3(String)} is the expected method of use, this object
* allows the HTML escaping functionality to be used as the foundation for a
* custom translator.
*
* @since 3.0
*/
public static final CharSequenceTranslator ESCAPE_HTML3 = new AggregateTranslator(new LookupTranslator(
EntityArrays.BASIC_ESCAPE()), new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()));
/**
* Translator object for escaping HTML version 4.0. While
* {@link #escapeHtml4(String)} is the expected method of use, this object
* allows the HTML escaping functionality to be used as the foundation for a
* custom translator.
*
* @since 3.0
*/
public static final CharSequenceTranslator ESCAPE_HTML4 = new AggregateTranslator(new LookupTranslator(
EntityArrays.BASIC_ESCAPE()), new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()), new LookupTranslator(
EntityArrays.HTML40_EXTENDED_ESCAPE()));
/* UNESCAPE TRANSLATORS */
/**
* Translator object for unescaping escaped HTML 3.0. While
* {@link #unescapeHtml3(String)} is the expected method of use, this object
* allows the HTML unescaping functionality to be used as the foundation for
* a custom translator.
*
* @since 3.0
*/
public static final CharSequenceTranslator UNESCAPE_HTML3 = new AggregateTranslator(new LookupTranslator(
EntityArrays.BASIC_UNESCAPE()), new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()),
new NumericEntityUnescaper());
/**
* Translator object for unescaping escaped HTML 4.0. While
* {@link #unescapeHtml4(String)} is the expected method of use, this object
* allows the HTML unescaping functionality to be used as the foundation for
* a custom translator.
*
* @since 3.0
*/
public static final CharSequenceTranslator UNESCAPE_HTML4 = new AggregateTranslator(new LookupTranslator(
EntityArrays.BASIC_UNESCAPE()), new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()),
new LookupTranslator(EntityArrays.HTML40_EXTENDED_UNESCAPE()), new NumericEntityUnescaper());
/* Helper functions */
/**
* <p>
* {@code StringEscapeUtils} instances should NOT be constructed in standard
* programming.
* </p>
* <p>
* Instead, the class should be used as:
*
* <pre>
* StringEscapeUtils.escapeJava(&quot;foo&quot;);
* </pre>
*
* </p>
* <p>
* This constructor is public to permit tools that require a JavaBean
* instance to operate.
* </p>
*/
public StringEscapeUtils() {
super();
}
/**
* <p>
* Escapes the characters in a {@code String} using HTML entities.
* </p>
* <p>
* Supports only the HTML 3.0 entities.
* </p>
*
* @param input the {@code String} to escape, may be null
* @return a new escaped {@code String}, {@code null} if null string input
* @since 3.0
*/
public static final String escapeHtml3(final String input) {
return ESCAPE_HTML3.translate(input);
}
// HTML and XML
// --------------------------------------------------------------------------
/**
* <p>
* Escapes the characters in a {@code String} using HTML entities.
* </p>
* <p>
* For example:
* </p>
* <p>
* <code>"bread" & "butter"</code>
* </p>
* becomes:
* <p>
* <code>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</code>
* .
* </p>
* <p>
* Supports all known HTML 4.0 entities, including funky accents. Note that
* the commonly used apostrophe escape character (&amp;apos;) is not a legal
* entity and so is not supported).
* </p>
*
* @param input the {@code String} to escape, may be null
* @return a new escaped {@code String}, {@code null} if null string input
* @see <a
* href="http://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO
* Entities</a>
* @see <a href="http://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character
* Entities for ISO Latin-1</a>
* @see <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">HTML
* 4.0 Character entity references</a>
* @see <a href="http://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01
* Character References</a>
* @see <a
* href="http://www.w3.org/TR/html401/charset.html#code-position">HTML
* 4.01 Code positions</a>
* @since 3.0
*/
public static final String escapeHtml4(final String input) {
return ESCAPE_HTML4.translate(input);
}
/**
* <p>
* Unescapes a string containing entity escapes to a string containing the
* actual Unicode characters corresponding to the escapes. Supports only
* HTML 3.0 entities.
* </p>
*
* @param input the {@code String} to unescape, may be null
* @return a new unescaped {@code String}, {@code null} if null string input
* @since 3.0
*/
public static final String unescapeHtml3(final String input) {
return UNESCAPE_HTML3.translate(input);
}
// -----------------------------------------------------------------------
/**
* <p>
* Unescapes a string containing entity escapes to a string containing the
* actual Unicode characters corresponding to the escapes. Supports HTML 4.0
* entities.
* </p>
* <p>
* For example, the string "&amp;lt;Fran&amp;ccedil;ais&amp;gt;" will become
* "&lt;Fran&ccedil;ais&gt;"
* </p>
* <p>
* If an entity is unrecognized, it is left alone, and inserted verbatim
* into the result string. e.g. "&amp;gt;&amp;zzzz;x" will become
* "&gt;&amp;zzzz;x".
* </p>
*
* @param input the {@code String} to unescape, may be null
* @return a new unescaped {@code String}, {@code null} if null string input
* @since 3.0
*/
public static final String unescapeHtml4(final String input) {
return UNESCAPE_HTML4.translate(input);
}
}

View File

@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3.text.translate;
import java.io.IOException;
import java.io.Writer;
/**
* Executes a sequence of translators one after the other. Execution ends
* whenever the first translator consumes codepoints from the input.
*
* @since 3.0
* @version $Id: AggregateTranslator.java 1088899 2011-04-05 05:31:27Z bayard $
*/
public class AggregateTranslator extends CharSequenceTranslator {
private final CharSequenceTranslator[] translators;
/**
* Specify the translators to be used at creation time.
*
* @param translators CharSequenceTranslator array to aggregate
*/
public AggregateTranslator(final CharSequenceTranslator... translators) {
this.translators = clone(translators);
}
/**
* The first translator to consume codepoints from the input is the
* 'winner'. Execution stops with the number of consumed codepoints being
* returned. {@inheritDoc}
*/
@Override
public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
for (final CharSequenceTranslator translator : translators) {
final int consumed = translator.translate(input, index, out);
if (consumed != 0) return consumed;
}
return 0;
}
/**
* <p>
* Shallow clones an array returning a typecast result and handling
* {@code null}.
* </p>
* <p>
* The objects in the array are not cloned, thus there is no special
* handling for multi-dimensional arrays.
* </p>
* <p>
* This method returns {@code null} for a {@code null} input array.
* </p>
*
* @param <T> the component type of the array
* @param array the array to shallow clone, may be {@code null}
* @return the cloned array, {@code null} if {@code null} input
*/
private static <T> T[] clone(final T[] array) {
if (array == null) return null;
return array.clone();
}
}

View File

@ -0,0 +1,127 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3.text.translate;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Locale;
/**
* An API for translating text. Its core use is to escape and unescape text.
* Because escaping and unescaping is completely contextual, the API does not
* present two separate signatures.
*
* @since 3.0
* @version $Id: CharSequenceTranslator.java 1146844 2011-07-14 18:49:51Z
* mbenson $
*/
public abstract class CharSequenceTranslator {
/**
* Helper for non-Writer usage.
*
* @param input CharSequence to be translated
* @return String output of translation
*/
public final String translate(final CharSequence input) {
if (input == null) return null;
try {
final StringWriter writer = new StringWriter(input.length() * 2);
translate(input, writer);
return writer.toString();
} catch (final IOException ioe) {
// this should never ever happen while writing to a StringWriter
throw new RuntimeException(ioe);
}
}
/**
* Translate a set of codepoints, represented by an int index into a
* CharSequence, into another set of codepoints. The number of codepoints
* consumed must be returned, and the only IOExceptions thrown must be from
* interacting with the Writer so that the top level API may reliable ignore
* StringWriter IOExceptions.
*
* @param input CharSequence that is being translated
* @param index int representing the current point of translation
* @param out Writer to translate the text to
* @return int count of codepoints consumed
* @throws IOException if and only if the Writer produces an IOException
*/
public abstract int translate(CharSequence input, int index, Writer out) throws IOException;
/**
* Translate an input onto a Writer. This is intentionally final as its
* algorithm is tightly coupled with the abstract method of this class.
*
* @param input CharSequence that is being translated
* @param out Writer to translate the text to
* @throws IOException if and only if the Writer produces an IOException
*/
public final void translate(final CharSequence input, final Writer out) throws IOException {
if (out == null) throw new IllegalArgumentException("The Writer must not be null");
if (input == null) return;
int pos = 0;
final int len = input.length();
while (pos < len) {
final int consumed = translate(input, pos, out);
if (consumed == 0) {
final char[] c = Character.toChars(Character.codePointAt(input, pos));
out.write(c);
pos += c.length;
continue;
}
// // contract with translators is that they have to understand
// codepoints
// // and they just took care of a surrogate pair
for (int pt = 0; pt < consumed; pt++) {
pos += Character.charCount(Character.codePointAt(input, pos));
}
}
}
/**
* Helper method to create a merger of this translator with another set of
* translators. Useful in customizing the standard functionality.
*
* @param translators CharSequenceTranslator array of translators to merge
* with this one
* @return CharSequenceTranslator merging this translator with the others
*/
public final CharSequenceTranslator with(final CharSequenceTranslator... translators) {
final CharSequenceTranslator[] newArray = new CharSequenceTranslator[translators.length + 1];
newArray[0] = this;
System.arraycopy(translators, 0, newArray, 1, translators.length);
return new AggregateTranslator(newArray);
}
/**
* <p>
* Returns an upper case hexadecimal <code>String</code> for the given
* character.
* </p>
*
* @param codepoint The codepoint to convert.
* @return An upper case hexadecimal <code>String</code>
*/
public static String hex(final int codepoint) {
return Integer.toHexString(codepoint).toUpperCase(Locale.ENGLISH);
}
}

View File

@ -0,0 +1,547 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3.text.translate;
/**
* Class holding various entity data for HTML and XML - generally for use with
* the LookupTranslator. All arrays are of length [*][2].
*
* @since 3.0
* @version $Id: EntityArrays.java 1088899 2011-04-05 05:31:27Z bayard $
*/
public class EntityArrays {
private static final String[][] ISO8859_1_ESCAPE = { { "\u00A0", "&nbsp;" }, // non-breaking
// space
{ "\u00A1", "&iexcl;" }, // inverted exclamation mark
{ "\u00A2", "&cent;" }, // cent sign
{ "\u00A3", "&pound;" }, // pound sign
{ "\u00A4", "&curren;" }, // currency sign
{ "\u00A5", "&yen;" }, // yen sign = yuan sign
{ "\u00A6", "&brvbar;" }, // broken bar = broken vertical bar
{ "\u00A7", "&sect;" }, // section sign
{ "\u00A8", "&uml;" }, // diaeresis = spacing diaeresis
{ "\u00A9", "&copy;" }, // © - copyright sign
{ "\u00AA", "&ordf;" }, // feminine ordinal indicator
{ "\u00AB", "&laquo;" }, // left-pointing double angle quotation
// mark = left pointing guillemet
{ "\u00AC", "&not;" }, // not sign
{ "\u00AD", "&shy;" }, // soft hyphen = discretionary hyphen
{ "\u00AE", "&reg;" }, // ® - registered trademark sign
{ "\u00AF", "&macr;" }, // macron = spacing macron = overline = APL
// overbar
{ "\u00B0", "&deg;" }, // degree sign
{ "\u00B1", "&plusmn;" }, // plus-minus sign = plus-or-minus sign
{ "\u00B2", "&sup2;" }, // superscript two = superscript digit two =
// squared
{ "\u00B3", "&sup3;" }, // superscript three = superscript digit
// three = cubed
{ "\u00B4", "&acute;" }, // acute accent = spacing acute
{ "\u00B5", "&micro;" }, // micro sign
{ "\u00B6", "&para;" }, // pilcrow sign = paragraph sign
{ "\u00B7", "&middot;" }, // middle dot = Georgian comma = Greek
// middle dot
{ "\u00B8", "&cedil;" }, // cedilla = spacing cedilla
{ "\u00B9", "&sup1;" }, // superscript one = superscript digit one
{ "\u00BA", "&ordm;" }, // masculine ordinal indicator
{ "\u00BB", "&raquo;" }, // right-pointing double angle quotation
// mark = right pointing guillemet
{ "\u00BC", "&frac14;" }, // vulgar fraction one quarter = fraction
// one quarter
{ "\u00BD", "&frac12;" }, // vulgar fraction one half = fraction one
// half
{ "\u00BE", "&frac34;" }, // vulgar fraction three quarters =
// fraction three quarters
{ "\u00BF", "&iquest;" }, // inverted question mark = turned
// question mark
{ "\u00C0", "&Agrave;" }, // À - uppercase A, grave accent
{ "\u00C1", "&Aacute;" }, // Á - uppercase A, acute accent
{ "\u00C2", "&Acirc;" }, // Â - uppercase A, circumflex accent
{ "\u00C3", "&Atilde;" }, // Ã - uppercase A, tilde
{ "\u00C4", "&Auml;" }, // Ä - uppercase A, umlaut
{ "\u00C5", "&Aring;" }, // Å - uppercase A, ring
{ "\u00C6", "&AElig;" }, // Æ - uppercase AE
{ "\u00C7", "&Ccedil;" }, // Ç - uppercase C, cedilla
{ "\u00C8", "&Egrave;" }, // È - uppercase E, grave accent
{ "\u00C9", "&Eacute;" }, // É - uppercase E, acute accent
{ "\u00CA", "&Ecirc;" }, // Ê - uppercase E, circumflex accent
{ "\u00CB", "&Euml;" }, // Ë - uppercase E, umlaut
{ "\u00CC", "&Igrave;" }, // Ì - uppercase I, grave accent
{ "\u00CD", "&Iacute;" }, // Í - uppercase I, acute accent
{ "\u00CE", "&Icirc;" }, // Î - uppercase I, circumflex accent
{ "\u00CF", "&Iuml;" }, // Ï - uppercase I, umlaut
{ "\u00D0", "&ETH;" }, // Ð - uppercase Eth, Icelandic
{ "\u00D1", "&Ntilde;" }, // Ñ - uppercase N, tilde
{ "\u00D2", "&Ograve;" }, // Ò - uppercase O, grave accent
{ "\u00D3", "&Oacute;" }, // Ó - uppercase O, acute accent
{ "\u00D4", "&Ocirc;" }, // Ô - uppercase O, circumflex accent
{ "\u00D5", "&Otilde;" }, // Õ - uppercase O, tilde
{ "\u00D6", "&Ouml;" }, // Ö - uppercase O, umlaut
{ "\u00D7", "&times;" }, // multiplication sign
{ "\u00D8", "&Oslash;" }, // Ø - uppercase O, slash
{ "\u00D9", "&Ugrave;" }, // Ù - uppercase U, grave accent
{ "\u00DA", "&Uacute;" }, // Ú - uppercase U, acute accent
{ "\u00DB", "&Ucirc;" }, // Û - uppercase U, circumflex accent
{ "\u00DC", "&Uuml;" }, // Ü - uppercase U, umlaut
{ "\u00DD", "&Yacute;" }, // Ý - uppercase Y, acute accent
{ "\u00DE", "&THORN;" }, // Þ - uppercase THORN, Icelandic
{ "\u00DF", "&szlig;" }, // ß - lowercase sharps, German
{ "\u00E0", "&agrave;" }, // à - lowercase a, grave accent
{ "\u00E1", "&aacute;" }, // á - lowercase a, acute accent
{ "\u00E2", "&acirc;" }, // â - lowercase a, circumflex accent
{ "\u00E3", "&atilde;" }, // ã - lowercase a, tilde
{ "\u00E4", "&auml;" }, // ä - lowercase a, umlaut
{ "\u00E5", "&aring;" }, // å - lowercase a, ring
{ "\u00E6", "&aelig;" }, // æ - lowercase ae
{ "\u00E7", "&ccedil;" }, // ç - lowercase c, cedilla
{ "\u00E8", "&egrave;" }, // è - lowercase e, grave accent
{ "\u00E9", "&eacute;" }, // é - lowercase e, acute accent
{ "\u00EA", "&ecirc;" }, // ê - lowercase e, circumflex accent
{ "\u00EB", "&euml;" }, // ë - lowercase e, umlaut
{ "\u00EC", "&igrave;" }, // ì - lowercase i, grave accent
{ "\u00ED", "&iacute;" }, // í - lowercase i, acute accent
{ "\u00EE", "&icirc;" }, // î - lowercase i, circumflex accent
{ "\u00EF", "&iuml;" }, // ï - lowercase i, umlaut
{ "\u00F0", "&eth;" }, // ð - lowercase eth, Icelandic
{ "\u00F1", "&ntilde;" }, // ñ - lowercase n, tilde
{ "\u00F2", "&ograve;" }, // ò - lowercase o, grave accent
{ "\u00F3", "&oacute;" }, // ó - lowercase o, acute accent
{ "\u00F4", "&ocirc;" }, // ô - lowercase o, circumflex accent
{ "\u00F5", "&otilde;" }, // õ - lowercase o, tilde
{ "\u00F6", "&ouml;" }, // ö - lowercase o, umlaut
{ "\u00F7", "&divide;" }, // division sign
{ "\u00F8", "&oslash;" }, // ø - lowercase o, slash
{ "\u00F9", "&ugrave;" }, // ù - lowercase u, grave accent
{ "\u00FA", "&uacute;" }, // ú - lowercase u, acute accent
{ "\u00FB", "&ucirc;" }, // û - lowercase u, circumflex accent
{ "\u00FC", "&uuml;" }, // ü - lowercase u, umlaut
{ "\u00FD", "&yacute;" }, // ý - lowercase y, acute accent
{ "\u00FE", "&thorn;" }, // þ - lowercase thorn, Icelandic
{ "\u00FF", "&yuml;" }, // ÿ - lowercase y, umlaut
};
private static final String[][] ISO8859_1_UNESCAPE = invert(ISO8859_1_ESCAPE);
private static final String[][] HTML40_EXTENDED_ESCAPE = {
// <!-- Latin Extended-B -->
{ "\u0192", "&fnof;" }, // latin small f with hook = function=
// florin, U+0192 ISOtech -->
// <!-- Greek -->
{ "\u0391", "&Alpha;" }, // greek capital letter alpha, U+0391 -->
{ "\u0392", "&Beta;" }, // greek capital letter beta, U+0392 -->
{ "\u0393", "&Gamma;" }, // greek capital letter gamma,U+0393
// ISOgrk3 -->
{ "\u0394", "&Delta;" }, // greek capital letter delta,U+0394
// ISOgrk3 -->
{ "\u0395", "&Epsilon;" }, // greek capital letter epsilon, U+0395
// -->
{ "\u0396", "&Zeta;" }, // greek capital letter zeta, U+0396 -->
{ "\u0397", "&Eta;" }, // greek capital letter eta, U+0397 -->
{ "\u0398", "&Theta;" }, // greek capital letter theta,U+0398
// ISOgrk3 -->
{ "\u0399", "&Iota;" }, // greek capital letter iota, U+0399 -->
{ "\u039A", "&Kappa;" }, // greek capital letter kappa, U+039A -->
{ "\u039B", "&Lambda;" }, // greek capital letter lambda,U+039B
// ISOgrk3 -->
{ "\u039C", "&Mu;" }, // greek capital letter mu, U+039C -->
{ "\u039D", "&Nu;" }, // greek capital letter nu, U+039D -->
{ "\u039E", "&Xi;" }, // greek capital letter xi, U+039E ISOgrk3 -->
{ "\u039F", "&Omicron;" }, // greek capital letter omicron, U+039F
// -->
{ "\u03A0", "&Pi;" }, // greek capital letter pi, U+03A0 ISOgrk3 -->
{ "\u03A1", "&Rho;" }, // greek capital letter rho, U+03A1 -->
// <!-- there is no Sigmaf, and no U+03A2
// character either -->
{ "\u03A3", "&Sigma;" }, // greek capital letter sigma,U+03A3
// ISOgrk3 -->
{ "\u03A4", "&Tau;" }, // greek capital letter tau, U+03A4 -->
{ "\u03A5", "&Upsilon;" }, // greek capital letter upsilon,U+03A5
// ISOgrk3 -->
{ "\u03A6", "&Phi;" }, // greek capital letter phi,U+03A6 ISOgrk3
// -->
{ "\u03A7", "&Chi;" }, // greek capital letter chi, U+03A7 -->
{ "\u03A8", "&Psi;" }, // greek capital letter psi,U+03A8 ISOgrk3
// -->
{ "\u03A9", "&Omega;" }, // greek capital letter omega,U+03A9
// ISOgrk3 -->
{ "\u03B1", "&alpha;" }, // greek small letter alpha,U+03B1 ISOgrk3
// -->
{ "\u03B2", "&beta;" }, // greek small letter beta, U+03B2 ISOgrk3
// -->
{ "\u03B3", "&gamma;" }, // greek small letter gamma,U+03B3 ISOgrk3
// -->
{ "\u03B4", "&delta;" }, // greek small letter delta,U+03B4 ISOgrk3
// -->
{ "\u03B5", "&epsilon;" }, // greek small letter epsilon,U+03B5
// ISOgrk3 -->
{ "\u03B6", "&zeta;" }, // greek small letter zeta, U+03B6 ISOgrk3
// -->
{ "\u03B7", "&eta;" }, // greek small letter eta, U+03B7 ISOgrk3 -->
{ "\u03B8", "&theta;" }, // greek small letter theta,U+03B8 ISOgrk3
// -->
{ "\u03B9", "&iota;" }, // greek small letter iota, U+03B9 ISOgrk3
// -->
{ "\u03BA", "&kappa;" }, // greek small letter kappa,U+03BA ISOgrk3
// -->
{ "\u03BB", "&lambda;" }, // greek small letter lambda,U+03BB
// ISOgrk3 -->
{ "\u03BC", "&mu;" }, // greek small letter mu, U+03BC ISOgrk3 -->
{ "\u03BD", "&nu;" }, // greek small letter nu, U+03BD ISOgrk3 -->
{ "\u03BE", "&xi;" }, // greek small letter xi, U+03BE ISOgrk3 -->
{ "\u03BF", "&omicron;" }, // greek small letter omicron, U+03BF NEW
// -->
{ "\u03C0", "&pi;" }, // greek small letter pi, U+03C0 ISOgrk3 -->
{ "\u03C1", "&rho;" }, // greek small letter rho, U+03C1 ISOgrk3 -->
{ "\u03C2", "&sigmaf;" }, // greek small letter final sigma,U+03C2
// ISOgrk3 -->
{ "\u03C3", "&sigma;" }, // greek small letter sigma,U+03C3 ISOgrk3
// -->
{ "\u03C4", "&tau;" }, // greek small letter tau, U+03C4 ISOgrk3 -->
{ "\u03C5", "&upsilon;" }, // greek small letter upsilon,U+03C5
// ISOgrk3 -->
{ "\u03C6", "&phi;" }, // greek small letter phi, U+03C6 ISOgrk3 -->
{ "\u03C7", "&chi;" }, // greek small letter chi, U+03C7 ISOgrk3 -->
{ "\u03C8", "&psi;" }, // greek small letter psi, U+03C8 ISOgrk3 -->
{ "\u03C9", "&omega;" }, // greek small letter omega,U+03C9 ISOgrk3
// -->
{ "\u03D1", "&thetasym;" }, // greek small letter theta
// symbol,U+03D1 NEW -->
{ "\u03D2", "&upsih;" }, // greek upsilon with hook symbol,U+03D2
// NEW -->
{ "\u03D6", "&piv;" }, // greek pi symbol, U+03D6 ISOgrk3 -->
// <!-- General Punctuation -->
{ "\u2022", "&bull;" }, // bullet = black small circle,U+2022 ISOpub
// -->
// <!-- bullet is NOT the same as bullet
// operator, U+2219 -->
{ "\u2026", "&hellip;" }, // horizontal ellipsis = three dot
// leader,U+2026 ISOpub -->
{ "\u2032", "&prime;" }, // prime = minutes = feet, U+2032 ISOtech
// -->
{ "\u2033", "&Prime;" }, // double prime = seconds = inches,U+2033
// ISOtech -->
{ "\u203E", "&oline;" }, // overline = spacing overscore,U+203E NEW
// -->
{ "\u2044", "&frasl;" }, // fraction slash, U+2044 NEW -->
// <!-- Letterlike Symbols -->
{ "\u2118", "&weierp;" }, // script capital P = power set=
// Weierstrass p, U+2118 ISOamso -->
{ "\u2111", "&image;" }, // blackletter capital I = imaginary
// part,U+2111 ISOamso -->
{ "\u211C", "&real;" }, // blackletter capital R = real part
// symbol,U+211C ISOamso -->
{ "\u2122", "&trade;" }, // trade mark sign, U+2122 ISOnum -->
{ "\u2135", "&alefsym;" }, // alef symbol = first transfinite
// cardinal,U+2135 NEW -->
// <!-- alef symbol is NOT the same as
// hebrew letter alef,U+05D0
// although the
// same glyph could be used to depict
// both characters -->
// <!-- Arrows -->
{ "\u2190", "&larr;" }, // leftwards arrow, U+2190 ISOnum -->
{ "\u2191", "&uarr;" }, // upwards arrow, U+2191 ISOnum-->
{ "\u2192", "&rarr;" }, // rightwards arrow, U+2192 ISOnum -->
{ "\u2193", "&darr;" }, // downwards arrow, U+2193 ISOnum -->
{ "\u2194", "&harr;" }, // left right arrow, U+2194 ISOamsa -->
{ "\u21B5", "&crarr;" }, // downwards arrow with corner leftwards=
// carriage return, U+21B5 NEW -->
{ "\u21D0", "&lArr;" }, // leftwards double arrow, U+21D0 ISOtech
// -->
// <!-- ISO 10646 does not say that lArr is
// the same as the 'is
// implied by'
// arrow but also does not have any other
// character for that
// function.
// So ? lArr canbe used for 'is implied by'
// as ISOtech suggests
// -->
{ "\u21D1", "&uArr;" }, // upwards double arrow, U+21D1 ISOamsa -->
{ "\u21D2", "&rArr;" }, // rightwards double arrow,U+21D2 ISOtech
// -->
// <!-- ISO 10646 does not say this is the
// 'implies' character
// but
// does not
// have another character with this function
// so ?rArr can be
// used
// for
// 'implies' as ISOtech suggests -->
{ "\u21D3", "&dArr;" }, // downwards double arrow, U+21D3 ISOamsa
// -->
{ "\u21D4", "&hArr;" }, // left right double arrow,U+21D4 ISOamsa
// -->
// <!-- Mathematical Operators -->
{ "\u2200", "&forall;" }, // for all, U+2200 ISOtech -->
{ "\u2202", "&part;" }, // partial differential, U+2202 ISOtech -->
{ "\u2203", "&exist;" }, // there exists, U+2203 ISOtech -->
{ "\u2205", "&empty;" }, // empty set = null set = diameter,U+2205
// ISOamso -->
{ "\u2207", "&nabla;" }, // nabla = backward difference,U+2207
// ISOtech -->
{ "\u2208", "&isin;" }, // element of, U+2208 ISOtech -->
{ "\u2209", "&notin;" }, // not an element of, U+2209 ISOtech -->
{ "\u220B", "&ni;" }, // contains as member, U+220B ISOtech -->
// <!-- should there be a more memorable
// name than 'ni'? -->
{ "\u220F", "&prod;" }, // n-ary product = product sign,U+220F
// ISOamsb -->
// <!-- prod is NOT the same character as
// U+03A0 'greek capital
// letter pi'
// though the same glyph might be used for
// both -->
{ "\u2211", "&sum;" }, // n-ary summation, U+2211 ISOamsb -->
// <!-- sum is NOT the same character as
// U+03A3 'greek capital
// letter sigma'
// though the same glyph might be used for
// both -->
{ "\u2212", "&minus;" }, // minus sign, U+2212 ISOtech -->
{ "\u2217", "&lowast;" }, // asterisk operator, U+2217 ISOtech -->
{ "\u221A", "&radic;" }, // square root = radical sign,U+221A
// ISOtech -->
{ "\u221D", "&prop;" }, // proportional to, U+221D ISOtech -->
{ "\u221E", "&infin;" }, // infinity, U+221E ISOtech -->
{ "\u2220", "&ang;" }, // angle, U+2220 ISOamso -->
{ "\u2227", "&and;" }, // logical and = wedge, U+2227 ISOtech -->
{ "\u2228", "&or;" }, // logical or = vee, U+2228 ISOtech -->
{ "\u2229", "&cap;" }, // intersection = cap, U+2229 ISOtech -->
{ "\u222A", "&cup;" }, // union = cup, U+222A ISOtech -->
{ "\u222B", "&int;" }, // integral, U+222B ISOtech -->
{ "\u2234", "&there4;" }, // therefore, U+2234 ISOtech -->
{ "\u223C", "&sim;" }, // tilde operator = varies with = similar
// to,U+223C ISOtech -->
// <!-- tilde operator is NOT the same
// character as the tilde,
// U+007E,although
// the same glyph might be used to represent
// both -->
{ "\u2245", "&cong;" }, // approximately equal to, U+2245 ISOtech
// -->
{ "\u2248", "&asymp;" }, // almost equal to = asymptotic to,U+2248
// ISOamsr -->
{ "\u2260", "&ne;" }, // not equal to, U+2260 ISOtech -->
{ "\u2261", "&equiv;" }, // identical to, U+2261 ISOtech -->
{ "\u2264", "&le;" }, // less-than or equal to, U+2264 ISOtech -->
{ "\u2265", "&ge;" }, // greater-than or equal to,U+2265 ISOtech -->
{ "\u2282", "&sub;" }, // subset of, U+2282 ISOtech -->
{ "\u2283", "&sup;" }, // superset of, U+2283 ISOtech -->
// <!-- note that nsup, 'not a superset of,
// U+2283' is not
// covered
// by the
// Symbol font encoding and is not included.
// Should it be, for
// symmetry?
// It is in ISOamsn --> <!ENTITY
// nsub", "8836"},
// not a subset of, U+2284 ISOamsn -->
{ "\u2286", "&sube;" }, // subset of or equal to, U+2286 ISOtech -->
{ "\u2287", "&supe;" }, // superset of or equal to,U+2287 ISOtech
// -->
{ "\u2295", "&oplus;" }, // circled plus = direct sum,U+2295 ISOamsb
// -->
{ "\u2297", "&otimes;" }, // circled times = vector product,U+2297
// ISOamsb -->
{ "\u22A5", "&perp;" }, // up tack = orthogonal to =
// perpendicular,U+22A5 ISOtech -->
{ "\u22C5", "&sdot;" }, // dot operator, U+22C5 ISOamsb -->
// <!-- dot operator is NOT the same
// character as U+00B7 middle
// dot
// -->
// <!-- Miscellaneous Technical -->
{ "\u2308", "&lceil;" }, // left ceiling = apl upstile,U+2308
// ISOamsc -->
{ "\u2309", "&rceil;" }, // right ceiling, U+2309 ISOamsc -->
{ "\u230A", "&lfloor;" }, // left floor = apl downstile,U+230A
// ISOamsc -->
{ "\u230B", "&rfloor;" }, // right floor, U+230B ISOamsc -->
{ "\u2329", "&lang;" }, // left-pointing angle bracket = bra,U+2329
// ISOtech -->
// <!-- lang is NOT the same character as
// U+003C 'less than' or
// U+2039 'single left-pointing angle
// quotation
// mark' -->
{ "\u232A", "&rang;" }, // right-pointing angle bracket = ket,U+232A
// ISOtech -->
// <!-- rang is NOT the same character as
// U+003E 'greater than'
// or
// U+203A
// 'single right-pointing angle quotation
// mark' -->
// <!-- Geometric Shapes -->
{ "\u25CA", "&loz;" }, // lozenge, U+25CA ISOpub -->
// <!-- Miscellaneous Symbols -->
{ "\u2660", "&spades;" }, // black spade suit, U+2660 ISOpub -->
// <!-- black here seems to mean filled
// as opposed to hollow -->
{ "\u2663", "&clubs;" }, // black club suit = shamrock,U+2663 ISOpub
// -->
{ "\u2665", "&hearts;" }, // black heart suit = valentine,U+2665
// ISOpub -->
{ "\u2666", "&diams;" }, // black diamond suit, U+2666 ISOpub -->
// <!-- Latin Extended-A -->
{ "\u0152", "&OElig;" }, // -- latin capital ligature OE,U+0152
// ISOlat2 -->
{ "\u0153", "&oelig;" }, // -- latin small ligature oe, U+0153
// ISOlat2 -->
// <!-- ligature is a misnomer, this is
// a separate character in
// some
// languages -->
{ "\u0160", "&Scaron;" }, // -- latin capital letter S with
// caron,U+0160 ISOlat2 -->
{ "\u0161", "&scaron;" }, // -- latin small letter s with
// caron,U+0161 ISOlat2 -->
{ "\u0178", "&Yuml;" }, // -- latin capital letter Y with
// diaeresis,U+0178 ISOlat2 -->
// <!-- Spacing Modifier Letters -->
{ "\u02C6", "&circ;" }, // -- modifier letter circumflex
// accent,U+02C6 ISOpub -->
{ "\u02DC", "&tilde;" }, // small tilde, U+02DC ISOdia -->
// <!-- General Punctuation -->
{ "\u2002", "&ensp;" }, // en space, U+2002 ISOpub -->
{ "\u2003", "&emsp;" }, // em space, U+2003 ISOpub -->
{ "\u2009", "&thinsp;" }, // thin space, U+2009 ISOpub -->
{ "\u200C", "&zwnj;" }, // zero width non-joiner,U+200C NEW RFC 2070
// -->
{ "\u200D", "&zwj;" }, // zero width joiner, U+200D NEW RFC 2070 -->
{ "\u200E", "&lrm;" }, // left-to-right mark, U+200E NEW RFC 2070
// -->
{ "\u200F", "&rlm;" }, // right-to-left mark, U+200F NEW RFC 2070
// -->
{ "\u2013", "&ndash;" }, // en dash, U+2013 ISOpub -->
{ "\u2014", "&mdash;" }, // em dash, U+2014 ISOpub -->
{ "\u2018", "&lsquo;" }, // left single quotation mark,U+2018 ISOnum
// -->
{ "\u2019", "&rsquo;" }, // right single quotation mark,U+2019
// ISOnum -->
{ "\u201A", "&sbquo;" }, // single low-9 quotation mark, U+201A NEW
// -->
{ "\u201C", "&ldquo;" }, // left double quotation mark,U+201C ISOnum
// -->
{ "\u201D", "&rdquo;" }, // right double quotation mark,U+201D
// ISOnum -->
{ "\u201E", "&bdquo;" }, // double low-9 quotation mark, U+201E NEW
// -->
{ "\u2020", "&dagger;" }, // dagger, U+2020 ISOpub -->
{ "\u2021", "&Dagger;" }, // double dagger, U+2021 ISOpub -->
{ "\u2030", "&permil;" }, // per mille sign, U+2030 ISOtech -->
{ "\u2039", "&lsaquo;" }, // single left-pointing angle quotation
// mark,U+2039 ISO proposed -->
// <!-- lsaquo is proposed but not yet
// ISO standardized -->
{ "\u203A", "&rsaquo;" }, // single right-pointing angle quotation
// mark,U+203A ISO proposed -->
// <!-- rsaquo is proposed but not yet
// ISO standardized -->
{ "\u20AC", "&euro;" }, // -- euro sign, U+20AC NEW -->
};
private static final String[][] HTML40_EXTENDED_UNESCAPE = invert(HTML40_EXTENDED_ESCAPE);
private static final String[][] BASIC_ESCAPE = { { "\"", "&quot;" }, // " -
// double-quote
{ "&", "&amp;" }, // & - ampersand
{ "<", "&lt;" }, // < - less-than
{ ">", "&gt;" }, // > - greater-than
};
private static final String[][] BASIC_UNESCAPE = invert(BASIC_ESCAPE);
/**
* Mapping to escape the basic XML and HTML character entities. Namely:
* {@code " & < >}
*
* @return the mapping table
*/
public static String[][] BASIC_ESCAPE() {
return BASIC_ESCAPE.clone();
}
/**
* Reverse of {@link #BASIC_ESCAPE()} for unescaping purposes.
*
* @return the mapping table
*/
public static String[][] BASIC_UNESCAPE() {
return BASIC_UNESCAPE.clone();
}
/**
* Mapping to escape additional <a
* href="http://www.w3.org/TR/REC-html40/sgml/entities.html">character
* entity references</a>. Note that this must be used with
* {@link #ISO8859_1_ESCAPE()} to get the full list of HTML 4.0 character
* entities.
*
* @return the mapping table
*/
public static String[][] HTML40_EXTENDED_ESCAPE() {
return HTML40_EXTENDED_ESCAPE.clone();
}
/**
* Reverse of {@link #HTML40_EXTENDED_ESCAPE()} for unescaping purposes.
*
* @return the mapping table
*/
public static String[][] HTML40_EXTENDED_UNESCAPE() {
return HTML40_EXTENDED_UNESCAPE.clone();
}
/**
* Used to invert an escape array into an unescape array
*
* @param array String[][] to be inverted
* @return String[][] inverted array
*/
public static String[][] invert(final String[][] array) {
final String[][] newarray = new String[array.length][2];
for (int i = 0; i < array.length; i++) {
newarray[i][0] = array[i][1];
newarray[i][1] = array[i][0];
}
return newarray;
}
/**
* Mapping to escape <a
* href="https://secure.wikimedia.org/wikipedia/en/wiki/ISO/IEC_8859-1"
* >ISO-8859-1</a> characters to their named HTML 3.x equivalents.
*
* @return the mapping table
*/
public static String[][] ISO8859_1_ESCAPE() {
return ISO8859_1_ESCAPE.clone();
}
/**
* Reverse of {@link #ISO8859_1_ESCAPE()} for unescaping purposes.
*
* @return the mapping table
*/
public static String[][] ISO8859_1_UNESCAPE() {
return ISO8859_1_UNESCAPE.clone();
}
}

View File

@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3.text.translate;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
/**
* Translates a value using a lookup table.
*
* @since 3.0
* @version $Id: LookupTranslator.java 1091096 2011-04-11 15:07:29Z mbenson $
*/
public class LookupTranslator extends CharSequenceTranslator {
private final HashMap<CharSequence, CharSequence> lookupMap;
private final int shortest;
private final int longest;
/**
* Define the lookup table to be used in translation
*
* @param lookup CharSequence[][] table of size [*][2]
*/
public LookupTranslator(final CharSequence[]... lookup) {
lookupMap = new HashMap<CharSequence, CharSequence>();
int _shortest = Integer.MAX_VALUE;
int _longest = 0;
if (lookup != null) {
for (final CharSequence[] seq : lookup) {
lookupMap.put(seq[0], seq[1]);
final int sz = seq[0].length();
if (sz < _shortest) {
_shortest = sz;
}
if (sz > _longest) {
_longest = sz;
}
}
}
shortest = _shortest;
longest = _longest;
}
/**
* {@inheritDoc}
*/
@Override
public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
int max = longest;
if (index + longest > input.length()) {
max = input.length() - index;
}
// descend so as to get a greedy algorithm
for (int i = max; i >= shortest; i--) {
final CharSequence subSeq = input.subSequence(index, index + i);
final CharSequence result = lookupMap.get(subSeq);
if (result != null) {
out.write(result.toString());
return i;
}
}
return 0;
}
}

View File

@ -0,0 +1,133 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3.text.translate;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.EnumSet;
/**
* Translate XML numeric entities of the form &#[xX]?\d+;? to the specific
* codepoint. Note that the semi-colon is optional.
*
* @since 3.0
* @version $Id: NumericEntityUnescaper.java 1199894 2011-11-09 17:53:59Z
* ggregory $
*/
public class NumericEntityUnescaper extends CharSequenceTranslator {
// TODO?: Create an OptionsSet class to hide some of the conditional logic
// below
private final EnumSet<OPTION> options;
/**
* Create a UnicodeUnescaper. The constructor takes a list of options, only
* one type of which is currently available (whether to allow, error or
* ignore the semi-colon on the end of a numeric entity to being missing).
* For example, to support numeric entities without a ';': new
* NumericEntityUnescaper(NumericEntityUnescaper.OPTION.semiColonOptional)
* and to throw an IllegalArgumentException when they're missing: new
* NumericEntityUnescaper(NumericEntityUnescaper.OPTION.errorIfNoSemiColon)
* Note that the default behaviour is to ignore them.
*
* @param options to apply to this unescaper
*/
public NumericEntityUnescaper(final OPTION... options) {
if (options.length > 0) {
this.options = EnumSet.copyOf(Arrays.asList(options));
} else {
this.options = EnumSet.copyOf(Arrays.asList(new OPTION[] { OPTION.semiColonRequired }));
}
}
/**
* Whether the passed in option is currently set.
*
* @param option to check state of
* @return whether the option is set
*/
public boolean isSet(final OPTION option) {
return options == null ? false : options.contains(option);
}
/**
* {@inheritDoc}
*/
@Override
public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
final int seqEnd = input.length();
// Uses -2 to ensure there is something after the &#
if (input.charAt(index) == '&' && index < seqEnd - 2 && input.charAt(index + 1) == '#') {
int start = index + 2;
boolean isHex = false;
final char firstChar = input.charAt(start);
if (firstChar == 'x' || firstChar == 'X') {
start++;
isHex = true;
// Check there's more than just an x after the &#
if (start == seqEnd) return 0;
}
int end = start;
// Note that this supports character codes without a ; on the end
while (end < seqEnd
&& (input.charAt(end) >= '0' && input.charAt(end) <= '9' || input.charAt(end) >= 'a'
&& input.charAt(end) <= 'f' || input.charAt(end) >= 'A' && input.charAt(end) <= 'F')) {
end++;
}
final boolean semiNext = end != seqEnd && input.charAt(end) == ';';
if (!semiNext) {
if (isSet(OPTION.semiColonRequired))
return 0;
else if (isSet(OPTION.errorIfNoSemiColon))
throw new IllegalArgumentException("Semi-colon required at end of numeric entity");
}
int entityValue;
try {
if (isHex) {
entityValue = Integer.parseInt(input.subSequence(start, end).toString(), 16);
} else {
entityValue = Integer.parseInt(input.subSequence(start, end).toString(), 10);
}
} catch (final NumberFormatException nfe) {
return 0;
}
if (entityValue > 0xFFFF) {
final char[] chrs = Character.toChars(entityValue);
out.write(chrs[0]);
out.write(chrs[1]);
} else {
out.write(entityValue);
}
return 2 + end - start + (isHex ? 1 : 0) + (semiNext ? 1 : 0);
}
return 0;
}
public static enum OPTION {
semiColonRequired, semiColonOptional, errorIfNoSemiColon
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2013 Jacek Marchwicki <jacek.marchwicki@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.dynamicgridview;
public interface DraggableAdapter {
void reorderElements(int position, int newPosition);
void swapElements(int position, int newPosition);
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.dynamicgridview;
import android.content.Context;
import org.mariotaku.twidere.adapter.ArrayAdapter;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
public class DraggableArrayAdapter<T> extends ArrayAdapter<T> implements DraggableAdapter {
final int INVALID_ID = -1;
private final HashMap<T, Integer> mIdMap = new HashMap<T, Integer>();
public DraggableArrayAdapter(final Context context, final int layoutRes) {
this(context, layoutRes, null);
}
public DraggableArrayAdapter(final Context context, final int layoutRes, final Collection<? extends T> collection) {
super(context, layoutRes, collection);
rebuildIdMap();
}
@Override
public void add(final T item) {
super.add(item);
rebuildIdMap();
}
@Override
public void addAll(final Collection<? extends T> collection) {
super.addAll(collection);
rebuildIdMap();
}
@Override
public void clear() {
super.clear();
rebuildIdMap();
}
@Override
public long getItemId(final int position) {
if (position < 0 || position >= mIdMap.size()) return INVALID_ID;
final T item = getItem(position);
return mIdMap.get(item);
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean remove(final int position) {
final boolean result = super.remove(position);
rebuildIdMap();
return result;
}
@Override
public void removeAll(final List<T> collection) {
super.removeAll(collection);
rebuildIdMap();
}
@Override
public void reorderElements(final int position, final int newPosition) {
final List<T> objects = mData;
T previous = objects.get(position);
final int iterator = newPosition < position ? 1 : -1;
final int afterPosition = position + iterator;
for (int cellPosition = newPosition; cellPosition != afterPosition; cellPosition += iterator) {
final T tmp = objects.get(cellPosition);
objects.set(cellPosition, previous);
previous = tmp;
}
notifyDataSetChanged();
}
@Override
public void sort(final Comparator<? super T> comparator) {
super.sort(comparator);
rebuildIdMap();
}
@Override
public void swapElements(final int position, final int newPosition) {
final List<T> objects = mData;
final T temp = objects.get(position);
objects.set(position, objects.get(newPosition));
objects.set(newPosition, temp);
notifyDataSetChanged();
}
private void rebuildIdMap() {
mIdMap.clear();
for (int i = 0, j = mData.size(); i < j; ++i) {
mIdMap.put(mData.get(i), i);
}
}
}

View File

@ -0,0 +1,647 @@
/*
* Copyright (C) 2013 The Android Open Source Project
* Copyright (C) 2013 Jacek Marchwicki <jacek.marchwicki@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.dynamicgridview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListAdapter;
import java.util.HashMap;
/**
* The dynamic gridview is an extension of gridview that supports cell dragging
* and swapping.
*
* This layout is in charge of positioning the hover cell in the correct
* location on the screen in response to user touch events. It uses the position
* of the hover cell to determine when two cells should be swapped. If two cells
* should be swapped, all the corresponding data set and layout changes are
* handled here.
*
* If no cell is selected, all the touch events are passed down to the gridview
* and behave normally. If one of the items in the gridview experiences a long
* press event, the contents of its current visible state are captured as a
* bitmap and its visibility is set to INVISIBLE. A hover cell is then created
* and added to this layout as an overlaying BitmapDrawable above the gridview.
* Once the hover cell is translated some distance to signify an item swap, a
* data set change accompanied by animation takes place. When the user releases
* the hover cell, it animates into its corresponding position in the gridview.
*
* When the hover cell is either above or below the bounds of the gridview, this
* gridview also scrolls on its own so as to reveal additional content.
*/
public class DynamicGridView extends GridView {
private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15;
private final int MOVE_DURATION = 150;
private final int LINE_THICKNESS = 15;
private int mLastEventX = -1;
private int mLastEventY = -1;
private int mDownY = -1;
private int mDownX = -1;
private final int mTotalOffsetX = 0;
private int mTotalOffsetY = 0;
private boolean mCellIsMobile = false;
private boolean mIsMobileScrolling = false;
private int mSmoothScrollAmountAtEdge = 0;
private final int INVALID_ID = -1;
private long mMobileItemId = INVALID_ID;
private BitmapDrawable mHoverCell;
private Rect mHoverCellCurrentBounds;
private Rect mHoverCellOriginalBounds;
private final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private boolean mIsWaitingForScrollFinish = false;
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
* Listens for long clicks on any items in the listview. When a cell has
* been selected, the hover cell is created and set up.
*/
private final OnItemLongClickListener mOnItemLongClickListener = new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(final AdapterView<?> arg0, final View arg1, final int pos, final long id) {
mTotalOffsetY = 0;
final int position = pointToPosition(mDownX, mDownY);
final int itemNum = position - getFirstVisiblePosition();
final View selectedView = getChildAt(itemNum);
mMobileItemId = getAdapter().getItemId(position);
mHoverCell = getAndAddHoverView(selectedView);
selectedView.setVisibility(INVISIBLE);
mCellIsMobile = true;
return true;
}
};
private final HashMap<Long, Integer> mItemIdTops = new HashMap<Long, Integer>();
private final HashMap<Long, Integer> mItemIdLefts = new HashMap<Long, Integer>();
/**
* This TypeEvaluator is used to animate the BitmapDrawable back to its
* final location when the user lifts his finger by modifying the
* BitmapDrawable's bounds.
*/
private final static TypeEvaluator<Rect> sBoundEvaluator = new TypeEvaluator<Rect>() {
@Override
public Rect evaluate(final float fraction, final Rect startValue, final Rect endValue) {
return new Rect(interpolate(startValue.left, endValue.left, fraction), interpolate(startValue.top,
endValue.top, fraction), interpolate(startValue.right, endValue.right, fraction), interpolate(
startValue.bottom, endValue.bottom, fraction));
}
public int interpolate(final int start, final int end, final float fraction) {
return (int) (start + fraction * (end - start));
}
};
/**
* This scroll listener is added to the gridview in order to handle cell
* swapping when the cell is either at the top or bottom edge of the
* gridview. If the hover cell is at either edge of the gridview, the
* gridview will begin scrolling. As scrolling takes place, the gridview
* continuously checks if new cells became visible and determines whether
* they are potential candidates for a cell swap.
*/
private final OnScrollListener mScrollListener = new OnScrollListener() {
private int mPreviousFirstVisibleItem = -1;
private int mPreviousVisibleItemCount = -1;
private int mCurrentFirstVisibleItem;
private int mCurrentVisibleItemCount;
private int mCurrentScrollState;
/**
* Determines if the gridview scrolled up enough to reveal a new cell at
* the top of the list. If so, then the appropriate parameters are
* updated.
*/
public void checkAndHandleFirstVisibleCellChange() {
if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
handleCellSwitch();
}
}
}
/**
* Determines if the gridview scrolled down enough to reveal a new cell
* at the bottom of the list. If so, then the appropriate parameters are
* updated.
*/
public void checkAndHandleLastVisibleCellChange() {
final int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
final int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
if (currentLastVisibleItem != previousLastVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
handleCellSwitch();
}
}
}
@Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) {
mCurrentFirstVisibleItem = firstVisibleItem;
mCurrentVisibleItemCount = visibleItemCount;
mPreviousFirstVisibleItem = mPreviousFirstVisibleItem == -1 ? mCurrentFirstVisibleItem
: mPreviousFirstVisibleItem;
mPreviousVisibleItemCount = mPreviousVisibleItemCount == -1 ? mCurrentVisibleItemCount
: mPreviousVisibleItemCount;
checkAndHandleFirstVisibleCellChange();
checkAndHandleLastVisibleCellChange();
mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
mPreviousVisibleItemCount = mCurrentVisibleItemCount;
}
@Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
mCurrentScrollState = scrollState;
mScrollState = scrollState;
isScrollCompleted();
}
/**
* This method is in charge of invoking 1 of 2 actions. Firstly, if the
* gridview is in a state of scrolling invoked by the hover cell being
* outside the bounds of the gridview, then this scrolling event is
* continued. Secondly, if the hover cell has already been released,
* this invokes the animation for the hover cell to return to its
* correct position after the gridview has entered an idle scroll state.
*/
private void isScrollCompleted() {
if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
if (mCellIsMobile && mIsMobileScrolling) {
handleMobileCellScroll();
} else if (mIsWaitingForScrollFinish) {
touchEventsEnded();
}
}
}
};
public DynamicGridView(final Context context) {
super(context);
init(context);
}
public DynamicGridView(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DynamicGridView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context);
}
/** Retrieves the position in the list corresponding to itemID */
public int getPositionForID(final long itemID) {
final View v = getViewForID(itemID);
if (v == null)
return -1;
else
return getPositionForView(v);
}
/** Retrieves the view in the list corresponding to itemID */
public View getViewForID(final long itemID) {
final int firstVisiblePosition = getFirstVisiblePosition();
final ListAdapter adapter = getAdapter();
for (int i = 0; i < getChildCount(); i++) {
final View v = getChildAt(i);
final int position = firstVisiblePosition + i;
final long id = adapter.getItemId(position);
if (id == itemID) return v;
}
return null;
};
public View getViewForPosition(final int position) {
if (position < 0) return null;
if (position >= getCount()) return null;
final ListAdapter adapter = getAdapter();
final long itemId = adapter.getItemId(position);
return getViewForID(itemId);
}
/**
* This method is in charge of determining if the hover cell is above or
* below the bounds of the gridview. If so, the gridview does an appropriate
* upward or downward smooth scroll so as to reveal new items.
*/
public boolean handleMobileCellScroll(final Rect r) {
final int offset = computeVerticalScrollOffset();
final int height = getHeight();
final int extent = computeVerticalScrollExtent();
final int range = computeVerticalScrollRange();
final int hoverViewTop = r.top;
final int hoverHeight = r.height();
if (hoverViewTop <= 0 && offset > 0) {
smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewTop + hoverHeight >= height && offset + extent < range) {
smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
public void init(final Context context) {
setOnItemLongClickListener(mOnItemLongClickListener);
setOnScrollListener(mScrollListener);
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mSmoothScrollAmountAtEdge = (int) (SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density);
}
@Override
public boolean onTouchEvent(final MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mDownX = (int) event.getX();
mDownY = (int) event.getY();
mActivePointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER_ID) {
break;
}
int pointerIndex = event.findPointerIndex(mActivePointerId);
mLastEventX = (int) event.getX(pointerIndex);
mLastEventY = (int) event.getY(pointerIndex);
final int deltaX = mLastEventX - mDownX;
final int deltaY = mLastEventY - mDownY;
if (mCellIsMobile) {
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left + deltaX + mTotalOffsetX,
mHoverCellOriginalBounds.top + deltaY + mTotalOffsetY);
mHoverCell.setBounds(mHoverCellCurrentBounds);
invalidate();
handleCellSwitch();
mIsMobileScrolling = false;
handleMobileCellScroll();
return false;
}
break;
case MotionEvent.ACTION_UP:
touchEventsEnded();
break;
case MotionEvent.ACTION_CANCEL:
touchEventsCancelled();
break;
case MotionEvent.ACTION_POINTER_UP:
/*
* If a multitouch event took place and the original touch
* dictating the movement of the hover cell has ended, then the
* dragging event ends and the hover cell is animated to its
* corresponding position in the gridview.
*/
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
touchEventsEnded();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public void setAdapter(final ListAdapter adapter) {
if (isInEditMode()) return;
if (!(adapter instanceof DraggableAdapter))
throw new IllegalArgumentException("Adapter have to implement DraggableAdapter");
super.setAdapter(adapter);
}
/**
* dispatchDraw gets invoked when all the child views are about to be drawn.
* By overriding this method, the hover cell (BitmapDrawable) can be drawn
* over the gridview's items whenever the gridview is redrawn.
*/
@Override
protected void dispatchDraw(final Canvas canvas) {
super.dispatchDraw(canvas);
if (mHoverCell != null) {
mHoverCell.draw(canvas);
}
}
/**
* Creates the hover cell with the appropriate bitmap and of appropriate
* size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
* single time an invalidate call is made.
*/
private BitmapDrawable getAndAddHoverView(final View v) {
final int w = v.getWidth();
final int h = v.getHeight();
final int top = v.getTop();
final int left = v.getLeft();
final Bitmap b = getBitmapWithBorder(v);
final BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
drawable.setBounds(mHoverCellCurrentBounds);
return drawable;
}
/** Returns a bitmap showing a screenshot of the view passed in. */
private Bitmap getBitmapFromView(final View v) {
final Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
v.draw(canvas);
return bitmap;
}
/** Draws a black border over the screenshot of the view passed in. */
private Bitmap getBitmapWithBorder(final View v) {
final Bitmap bitmap = getBitmapFromView(v);
final Canvas can = new Canvas(bitmap);
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(LINE_THICKNESS);
paint.setColor(Color.BLACK);
can.drawBitmap(bitmap, 0, 0, null);
can.drawRect(rect, paint);
return bitmap;
}
/**
* This method determines whether the hover cell has been shifted far enough
* to invoke a cell swap. If so, then the respective cell swap candidate is
* determined and the data set is changed. Upon posting a notification of
* the data set change, a layout is invoked to place the cells in the right
* place. Using a ViewTreeObserver and a corresponding OnPreDrawListener, we
* can offset the cell being swapped to where it previously was and then
* animate it to its new position.
*/
private void handleCellSwitch() {
final int deltaY = mLastEventY - mDownY;
final int deltaX = mLastEventX - mDownX;
final int deltaYTotal = mHoverCellOriginalBounds.top + mTotalOffsetY + deltaY;
final int deltaXTotal = mHoverCellOriginalBounds.left + mTotalOffsetX + deltaX;
final int numColumns = getNumColumns();
final int position = getPositionForID(mMobileItemId);
final int abovePosition = position - numColumns;
final int belowPosition = position + numColumns;
final int toLeftPosition = position - 1;
final int toRightPosition = position + 1;
final View aboveView = getViewForPosition(abovePosition);
final View belowView = getViewForPosition(belowPosition);
View toLeftView = getViewForPosition(toLeftPosition);
View toRightView = getViewForPosition(toRightPosition);
final View mobileView = getViewForID(mMobileItemId);
if (toRightView != null && mobileView.getLeft() > toRightView.getLeft()) {
// mobile view is far right
toRightView = null;
}
if (toLeftView != null && mobileView.getLeft() < toLeftView.getLeft()) {
// mobile view is far left
toLeftView = null;
}
final boolean isBelow = belowView != null && deltaYTotal > belowView.getTop();
final boolean isAbove = aboveView != null && deltaYTotal < aboveView.getTop();
final boolean isToRight = toRightView != null && deltaXTotal > toRightView.getLeft();
final boolean isToLeft = toLeftView != null && deltaXTotal < toLeftView.getLeft();
int newPosition;
if (isBelow) {
newPosition = belowPosition;
} else if (isAbove) {
newPosition = abovePosition;
} else if (isToLeft) {
newPosition = toLeftPosition;
} else if (isToRight) {
newPosition = toRightPosition;
} else {
newPosition = position;
}
if (newPosition == position) return;
final ListAdapter adapter = getAdapter();
final int fromPosition = Math.min(newPosition, position);
final int toPosition = Math.max(newPosition, position);
for (int cellPosition = fromPosition; cellPosition <= toPosition; cellPosition++) {
getViewForPosition(cellPosition).setVisibility(View.VISIBLE);
}
mItemIdLefts.clear();
mItemIdTops.clear();
final int firstVisiblePosition = getFirstVisiblePosition();
final int childCount = getChildCount();
for (int childAt = 0; childAt < childCount; childAt++) {
final View child = getChildAt(childAt);
assert child != null;
final int pos = firstVisiblePosition + childAt;
final long itemId = adapter.getItemId(pos);
mItemIdLefts.put(itemId, child.getLeft());
mItemIdTops.put(itemId, child.getTop());
}
final ViewTreeObserver observer = getViewTreeObserver();
assert observer != null;
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
final int firstVisiblePosition = getFirstVisiblePosition();
final int childCount = getChildCount();
for (int childAt = 0; childAt < childCount; childAt++) {
final View child = getChildAt(childAt);
assert child != null;
final int pos = firstVisiblePosition + childAt;
final long itemId = adapter.getItemId(pos);
if (itemId == mMobileItemId) {
child.setVisibility(View.GONE);
continue;
}
final Integer oldLeft = mItemIdLefts.get(itemId);
final Integer oldTop = mItemIdTops.get(itemId);
if (oldLeft == null) {
continue;
}
mItemIdLefts.put(itemId, child.getLeft());
mItemIdTops.put(itemId, child.getTop());
final int newLeft = child.getLeft();
final int newTop = child.getTop();
final int deltaX = oldLeft - newLeft;
final int deltaY = oldTop - newTop;
if (deltaX == 0 && deltaY == 0) {
continue;
}
child.setTranslationX(deltaX);
child.setTranslationY(deltaY);
final AnimatorSet animator = new AnimatorSet();
animator.playTogether(ObjectAnimator.ofFloat(child, View.TRANSLATION_X, 0),
ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, 0));
animator.setDuration(MOVE_DURATION).start();
}
return true;
}
});
((DraggableAdapter) adapter).reorderElements(position, newPosition);
}
/**
* Determines whether this gridview is in a scrolling state invoked by the
* fact that the hover cell is out of the bounds of the gridview;
*/
private void handleMobileCellScroll() {
mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
}
/**
* Resets all the appropriate fields to a default state.
*/
private void touchEventsCancelled() {
final View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile) {
mMobileItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
invalidate();
}
mCellIsMobile = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
}
/**
* Resets all the appropriate fields to a default state while also animating
* the hover cell back to its correct location.
*/
private void touchEventsEnded() {
final View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile || mIsWaitingForScrollFinish) {
mCellIsMobile = false;
mIsWaitingForScrollFinish = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
// If the autoscroller has not completed scrolling, we need to wait
// for it to
// finish in order to determine the final location of where the
// hover cell
// should be animated to.
if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
mIsWaitingForScrollFinish = true;
return;
}
mHoverCellCurrentBounds.offsetTo(mobileView.getLeft(), mobileView.getTop());
final ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds", sBoundEvaluator,
mHoverCellCurrentBounds);
hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator valueAnimator) {
invalidate();
}
});
hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animation) {
mMobileItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
setEnabled(true);
invalidate();
}
@Override
public void onAnimationStart(final Animator animation) {
setEnabled(false);
}
});
hoverViewAnimator.start();
} else {
touchEventsCancelled();
}
}
}

View File

@ -0,0 +1,588 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.dynamicgridview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
/**
* The dynamic listview is an extension of listview that supports cell dragging
* and swapping.
*
* This layout is in charge of positioning the hover cell in the correct
* location on the screen in response to user touch events. It uses the position
* of the hover cell to determine when two cells should be swapped. If two cells
* should be swapped, all the corresponding data set and layout changes are
* handled here.
*
* If no cell is selected, all the touch events are passed down to the listview
* and behave normally. If one of the items in the listview experiences a long
* press event, the contents of its current visible state are captured as a
* bitmap and its visibility is set to INVISIBLE. A hover cell is then created
* and added to this layout as an overlaying BitmapDrawable above the listview.
* Once the hover cell is translated some distance to signify an item swap, a
* data set change accompanied by animation takes place. When the user releases
* the hover cell, it animates into its corresponding position in the listview.
*
* When the hover cell is either above or below the bounds of the listview, this
* listview also scrolls on its own so as to reveal additional content.
*/
public class DynamicListView extends ListView {
private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15;
private final int MOVE_DURATION = 150;
private final int LINE_THICKNESS = 15;
private int mLastEventY = -1;
private int mDownY = -1;
private int mDownX = -1;
private int mTotalOffset = 0;
private boolean mCellIsMobile = false;
private boolean mIsMobileScrolling = false;
private int mSmoothScrollAmountAtEdge = 0;
private final int INVALID_ID = -1;
private long mAboveItemId = INVALID_ID;
private long mMobileItemId = INVALID_ID;
private long mBelowItemId = INVALID_ID;
private BitmapDrawable mHoverCell;
private Rect mHoverCellCurrentBounds;
private Rect mHoverCellOriginalBounds;
private final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private boolean mIsWaitingForScrollFinish = false;
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
* Listens for long clicks on any items in the listview. When a cell has
* been selected, the hover cell is created and set up.
*/
private final AdapterView.OnItemLongClickListener mOnItemLongClickListener = new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(final AdapterView<?> arg0, final View arg1, final int pos, final long id) {
mTotalOffset = 0;
final int position = pointToPosition(mDownX, mDownY);
final int itemNum = position - getFirstVisiblePosition();
final View selectedView = getChildAt(itemNum);
mMobileItemId = getAdapter().getItemId(position);
mHoverCell = getAndAddHoverView(selectedView);
selectedView.setVisibility(INVISIBLE);
mCellIsMobile = true;
updateNeighborViewsForID(mMobileItemId);
return true;
}
};
/**
* This TypeEvaluator is used to animate the BitmapDrawable back to its
* final location when the user lifts his finger by modifying the
* BitmapDrawable's bounds.
*/
private final static TypeEvaluator<Rect> sBoundEvaluator = new TypeEvaluator<Rect>() {
@Override
public Rect evaluate(final float fraction, final Rect startValue, final Rect endValue) {
return new Rect(interpolate(startValue.left, endValue.left, fraction), interpolate(startValue.top,
endValue.top, fraction), interpolate(startValue.right, endValue.right, fraction), interpolate(
startValue.bottom, endValue.bottom, fraction));
}
public int interpolate(final int start, final int end, final float fraction) {
return (int) (start + fraction * (end - start));
}
};
/**
* This scroll listener is added to the listview in order to handle cell
* swapping when the cell is either at the top or bottom edge of the
* listview. If the hover cell is at either edge of the listview, the
* listview will begin scrolling. As scrolling takes place, the listview
* continuously checks if new cells became visible and determines whether
* they are potential candidates for a cell swap.
*/
private final AbsListView.OnScrollListener mScrollListener = new AbsListView.OnScrollListener() {
private int mPreviousFirstVisibleItem = -1;
private int mPreviousVisibleItemCount = -1;
private int mCurrentFirstVisibleItem;
private int mCurrentVisibleItemCount;
private int mCurrentScrollState;
/**
* Determines if the listview scrolled up enough to reveal a new cell at
* the top of the list. If so, then the appropriate parameters are
* updated.
*/
public void checkAndHandleFirstVisibleCellChange() {
if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForID(mMobileItemId);
handleCellSwitch();
}
}
}
/**
* Determines if the listview scrolled down enough to reveal a new cell
* at the bottom of the list. If so, then the appropriate parameters are
* updated.
*/
public void checkAndHandleLastVisibleCellChange() {
final int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
final int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
if (currentLastVisibleItem != previousLastVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForID(mMobileItemId);
handleCellSwitch();
}
}
}
@Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) {
mCurrentFirstVisibleItem = firstVisibleItem;
mCurrentVisibleItemCount = visibleItemCount;
mPreviousFirstVisibleItem = mPreviousFirstVisibleItem == -1 ? mCurrentFirstVisibleItem
: mPreviousFirstVisibleItem;
mPreviousVisibleItemCount = mPreviousVisibleItemCount == -1 ? mCurrentVisibleItemCount
: mPreviousVisibleItemCount;
checkAndHandleFirstVisibleCellChange();
checkAndHandleLastVisibleCellChange();
mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
mPreviousVisibleItemCount = mCurrentVisibleItemCount;
}
@Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
mCurrentScrollState = scrollState;
mScrollState = scrollState;
isScrollCompleted();
}
/**
* This method is in charge of invoking 1 of 2 actions. Firstly, if the
* listview is in a state of scrolling invoked by the hover cell being
* outside the bounds of the listview, then this scrolling event is
* continued. Secondly, if the hover cell has already been released,
* this invokes the animation for the hover cell to return to its
* correct position after the listview has entered an idle scroll state.
*/
private void isScrollCompleted() {
if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
if (mCellIsMobile && mIsMobileScrolling) {
handleMobileCellScroll();
} else if (mIsWaitingForScrollFinish) {
touchEventsEnded();
}
}
}
};
public DynamicListView(final Context context) {
super(context);
init(context);
}
public DynamicListView(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DynamicListView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context);
}
/** Retrieves the position in the list corresponding to itemID */
public int getPositionForID(final long itemID) {
final View v = getViewForID(itemID);
if (v == null)
return -1;
else
return getPositionForView(v);
}
/** Retrieves the view in the list corresponding to itemID */
public View getViewForID(final long itemID) {
final int firstVisiblePosition = getFirstVisiblePosition();
final ListAdapter adapter = getAdapter();
for (int i = 0; i < getChildCount(); i++) {
final View v = getChildAt(i);
final int position = firstVisiblePosition + i;
final long id = adapter.getItemId(position);
if (id == itemID) return v;
}
return null;
}
/**
* This method is in charge of determining if the hover cell is above or
* below the bounds of the listview. If so, the listview does an appropriate
* upward or downward smooth scroll so as to reveal new items.
*/
public boolean handleMobileCellScroll(final Rect r) {
final int offset = computeVerticalScrollOffset();
final int height = getHeight();
final int extent = computeVerticalScrollExtent();
final int range = computeVerticalScrollRange();
final int hoverViewTop = r.top;
final int hoverHeight = r.height();
if (hoverViewTop <= 0 && offset > 0) {
smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewTop + hoverHeight >= height && offset + extent < range) {
smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
public void init(final Context context) {
setOnItemLongClickListener(mOnItemLongClickListener);
setOnScrollListener(mScrollListener);
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mSmoothScrollAmountAtEdge = (int) (SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density);
}
@Override
public boolean onTouchEvent(final MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mDownX = (int) event.getX();
mDownY = (int) event.getY();
mActivePointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER_ID) {
break;
}
int pointerIndex = event.findPointerIndex(mActivePointerId);
mLastEventY = (int) event.getY(pointerIndex);
final int deltaY = mLastEventY - mDownY;
if (mCellIsMobile) {
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mHoverCellOriginalBounds.top
+ deltaY + mTotalOffset);
mHoverCell.setBounds(mHoverCellCurrentBounds);
invalidate();
handleCellSwitch();
mIsMobileScrolling = false;
handleMobileCellScroll();
return false;
}
break;
case MotionEvent.ACTION_UP:
touchEventsEnded();
break;
case MotionEvent.ACTION_CANCEL:
touchEventsCancelled();
break;
case MotionEvent.ACTION_POINTER_UP:
/*
* If a multitouch event took place and the original touch
* dictating the movement of the hover cell has ended, then the
* dragging event ends and the hover cell is animated to its
* corresponding position in the listview.
*/
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
touchEventsEnded();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public void setAdapter(final ListAdapter adapter) {
if (!(adapter instanceof DraggableAdapter))
throw new IllegalArgumentException("Adapter have to implement DraggableAdapter");
super.setAdapter(adapter);
}
/**
* dispatchDraw gets invoked when all the child views are about to be drawn.
* By overriding this method, the hover cell (BitmapDrawable) can be drawn
* over the listview's items whenever the listview is redrawn.
*/
@Override
protected void dispatchDraw(final Canvas canvas) {
super.dispatchDraw(canvas);
if (mHoverCell != null) {
mHoverCell.draw(canvas);
}
}
/**
* Creates the hover cell with the appropriate bitmap and of appropriate
* size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
* single time an invalidate call is made.
*/
private BitmapDrawable getAndAddHoverView(final View v) {
final int w = v.getWidth();
final int h = v.getHeight();
final int top = v.getTop();
final int left = v.getLeft();
final Bitmap b = getBitmapWithBorder(v);
final BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
drawable.setBounds(mHoverCellCurrentBounds);
return drawable;
}
/** Returns a bitmap showing a screenshot of the view passed in. */
private Bitmap getBitmapFromView(final View v) {
final Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
v.draw(canvas);
return bitmap;
}
/** Draws a black border over the screenshot of the view passed in. */
private Bitmap getBitmapWithBorder(final View v) {
final Bitmap bitmap = getBitmapFromView(v);
final Canvas can = new Canvas(bitmap);
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(LINE_THICKNESS);
paint.setColor(Color.BLACK);
can.drawBitmap(bitmap, 0, 0, null);
can.drawRect(rect, paint);
return bitmap;
}
/**
* This method determines whether the hover cell has been shifted far enough
* to invoke a cell swap. If so, then the respective cell swap candidate is
* determined and the data set is changed. Upon posting a notification of
* the data set change, a layout is invoked to place the cells in the right
* place. Using a ViewTreeObserver and a corresponding OnPreDrawListener, we
* can offset the cell being swapped to where it previously was and then
* animate it to its new position.
*/
private void handleCellSwitch() {
final int deltaY = mLastEventY - mDownY;
final int deltaYTotal = mHoverCellOriginalBounds.top + mTotalOffset + deltaY;
final View belowView = getViewForID(mBelowItemId);
final View mobileView = getViewForID(mMobileItemId);
final View aboveView = getViewForID(mAboveItemId);
final boolean isBelow = belowView != null && deltaYTotal > belowView.getTop();
final boolean isAbove = aboveView != null && deltaYTotal < aboveView.getTop();
if (isBelow || isAbove) {
final long switchItemID = isBelow ? mBelowItemId : mAboveItemId;
final View switchView = isBelow ? belowView : aboveView;
final int originalItem = getPositionForView(mobileView);
if (switchView == null) {
updateNeighborViewsForID(mMobileItemId);
return;
}
final int newItemPosition = getPositionForID(switchItemID);
((DraggableAdapter) getAdapter()).swapElements(originalItem, newItemPosition);
mDownY = mLastEventY;
final int switchViewStartTop = switchView.getTop();
mobileView.setVisibility(View.VISIBLE);
switchView.setVisibility(View.INVISIBLE);
updateNeighborViewsForID(mMobileItemId);
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
final View switchView = getViewForID(switchItemID);
mTotalOffset += deltaY;
final int switchViewNewTop = switchView.getTop();
final int delta = switchViewStartTop - switchViewNewTop;
switchView.setTranslationY(delta);
final ObjectAnimator animator = ObjectAnimator.ofFloat(switchView, View.TRANSLATION_Y, 0);
animator.setDuration(MOVE_DURATION);
animator.start();
return true;
}
});
}
}
/**
* Determines whether this listview is in a scrolling state invoked by the
* fact that the hover cell is out of the bounds of the listview;
*/
private void handleMobileCellScroll() {
mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
}
/**
* Resets all the appropriate fields to a default state.
*/
private void touchEventsCancelled() {
final View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile) {
mAboveItemId = INVALID_ID;
mMobileItemId = INVALID_ID;
mBelowItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
invalidate();
}
mCellIsMobile = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
}
/**
* Resets all the appropriate fields to a default state while also animating
* the hover cell back to its correct location.
*/
private void touchEventsEnded() {
final View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile || mIsWaitingForScrollFinish) {
mCellIsMobile = false;
mIsWaitingForScrollFinish = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
// If the autoscroller has not completed scrolling, we need to wait
// for it to
// finish in order to determine the final location of where the
// hover cell
// should be animated to.
if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
mIsWaitingForScrollFinish = true;
return;
}
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mobileView.getTop());
final ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds", sBoundEvaluator,
mHoverCellCurrentBounds);
hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator valueAnimator) {
invalidate();
}
});
hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animation) {
mAboveItemId = INVALID_ID;
mMobileItemId = INVALID_ID;
mBelowItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
setEnabled(true);
invalidate();
}
@Override
public void onAnimationStart(final Animator animation) {
setEnabled(false);
}
});
hoverViewAnimator.start();
} else {
touchEventsCancelled();
}
}
/**
* Stores a reference to the views above and below the item currently
* corresponding to the hover cell. It is important to note that if this
* item is either at the top or bottom of the list, mAboveItemId or
* mBelowItemId may be invalid.
*/
private void updateNeighborViewsForID(final long itemID) {
final int position = getPositionForID(itemID);
final ListAdapter adapter = getAdapter();
mAboveItemId = adapter.getItemId(position - 1);
mBelowItemId = adapter.getItemId(position + 1);
}
}

View File

@ -0,0 +1,308 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2013 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.gallery3d;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Handler;
import android.support.v4.content.AsyncTaskLoader;
import android.util.DisplayMetrics;
import com.nostra13.universalimageloader.cache.disc.DiskCache;
import com.nostra13.universalimageloader.core.download.ImageDownloader;
import org.mariotaku.gallery3d.util.BitmapUtils;
import org.mariotaku.gallery3d.util.GalleryUtils;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.util.Exif;
import org.mariotaku.twidere.util.ImageValidator;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.imageloader.AccountExtra;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class GLImageLoader extends AsyncTaskLoader<GLImageLoader.Result> implements Constants {
private final Uri mUri;
private final Handler mHandler;
private final DownloadListener mListener;
private final ImageDownloader mDownloader;
private final DiskCache mDiskCache;
private final float mFallbackSize;
private final long mAccountId;
public GLImageLoader(final Context context, final DownloadListener listener, final long accountId, final Uri uri) {
super(context);
mHandler = new Handler();
mAccountId = accountId;
mUri = uri;
mListener = listener;
final TwidereApplication app = TwidereApplication.getInstance(context);
mDownloader = app.getFullImageDownloader();
mDiskCache = app.getFullDiskCache();
final Resources res = context.getResources();
final DisplayMetrics dm = res.getDisplayMetrics();
mFallbackSize = Math.max(dm.heightPixels, dm.widthPixels);
}
@Override
public GLImageLoader.Result loadInBackground() {
if (mUri == null) {
Result.nullInstance();
}
final String scheme = mUri.getScheme();
if ("http".equals(scheme) || "https".equals(scheme)) {
final String url = ParseUtils.parseString(mUri.toString());
if (url == null) return Result.nullInstance();
final File cacheFile = mDiskCache.get(url);
if (cacheFile != null) {
final File cacheDir = cacheFile.getParentFile();
if (cacheDir != null && !cacheDir.exists()) {
cacheDir.mkdirs();
}
} else
return Result.nullInstance();
try {
// from SD cache
if (ImageValidator.checkImageValidity(cacheFile)) return decodeImageInternal(cacheFile);
final InputStream is = mDownloader.getStream(url, new AccountExtra(mAccountId));
if (is == null) return Result.nullInstance();
final long length = is.available();
mHandler.post(new DownloadStartRunnable(this, mListener, length));
final OutputStream os = new FileOutputStream(cacheFile);
try {
dump(is, os);
mHandler.post(new DownloadFinishRunnable(this, mListener));
} finally {
GalleryUtils.closeSilently(is);
GalleryUtils.closeSilently(os);
}
if (!ImageValidator.checkImageValidity(cacheFile)) {
// The file is corrupted, so we remove it from
// cache.
final Result result = decodeBitmapOnly(cacheFile);
if (cacheFile.isFile()) {
cacheFile.delete();
}
return result;
}
return decodeImageInternal(cacheFile);
} catch (final Exception e) {
mHandler.post(new DownloadErrorRunnable(this, mListener, e));
return Result.getInstance(cacheFile, e);
}
} else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
final File file = new File(mUri.getPath());
try {
return decodeImage(file);
} catch (final Exception e) {
return Result.getInstance(file, e);
}
}
return Result.nullInstance();
}
protected Result decodeBitmapOnly(final File file) {
final String path = file.getAbsolutePath();
final BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
o.inPreferredConfig = Bitmap.Config.RGB_565;
BitmapFactory.decodeFile(path, o);
final int width = o.outWidth, height = o.outHeight;
if (width <= 0 || height <= 0) return Result.getInstance(file, null);
o.inJustDecodeBounds = false;
o.inSampleSize = BitmapUtils.computeSampleSize(mFallbackSize / Math.max(width, height));
final Bitmap bitmap = BitmapFactory.decodeFile(path, o);
return Result.getInstance(bitmap, Exif.getOrientation(file), file);
}
protected Result decodeImage(final File file) {
final String path = file.getAbsolutePath();
try {
final BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(path, false);
final int width = decoder.getWidth();
final int height = decoder.getHeight();
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = BitmapUtils.computeSampleSize(mFallbackSize / Math.max(width, height));
options.inPreferredConfig = Bitmap.Config.RGB_565;
final Bitmap bitmap = decoder.decodeRegion(new Rect(0, 0, width, height), options);
return Result.getInstance(decoder, bitmap, Exif.getOrientation(file), file);
} catch (final IOException e) {
return decodeBitmapOnly(file);
}
}
@Override
protected void onStartLoading() {
forceLoad();
}
private Result decodeImageInternal(final File file) throws IOException {
if (ImageValidator.checkImageValidity(file)) return decodeImage(file);
throw new InvalidImageException();
}
private void dump(final InputStream is, final OutputStream os) throws IOException {
final byte buffer[] = new byte[1024];
int rc = is.read(buffer, 0, buffer.length);
long downloaded = 0;
while (rc > 0) {
downloaded += rc;
mHandler.post(new ProgressUpdateRunnable(mListener, downloaded));
os.write(buffer, 0, rc);
rc = is.read(buffer, 0, buffer.length);
}
}
public static interface DownloadListener {
void onDownloadError(Throwable t);
void onDownloadFinished();
void onDownloadStart(long total);
void onProgressUpdate(long downloaded);
}
public static class InvalidImageException extends IOException {
private static final long serialVersionUID = 8996099908714452289L;
}
public static class Result {
public final Bitmap bitmap;
public final File file;
public final Exception exception;
public final BitmapRegionDecoder decoder;
public final int orientation;
public Result(final BitmapRegionDecoder decoder, final Bitmap bitmap, final int orientation, final File file,
final Exception exception) {
this.bitmap = bitmap;
this.file = file;
this.decoder = decoder;
this.orientation = orientation;
this.exception = exception;
}
public static Result getInstance(final Bitmap bitmap, final int orientation, final File file) {
return new Result(null, bitmap, orientation, file, null);
}
public static Result getInstance(final BitmapRegionDecoder decoder, final Bitmap bitmap, final int orientation,
final File file) {
return new Result(decoder, bitmap, orientation, file, null);
}
public static Result getInstance(final File file, final Exception e) {
return new Result(null, null, 0, file, e);
}
public static Result nullInstance() {
return new Result(null, null, 0, null, null);
}
}
private final static class DownloadErrorRunnable implements Runnable {
private final GLImageLoader loader;
private final DownloadListener listener;
private final Throwable t;
DownloadErrorRunnable(final GLImageLoader loader, final DownloadListener listener, final Throwable t) {
this.loader = loader;
this.listener = listener;
this.t = t;
}
@Override
public void run() {
if (listener == null || loader.isAbandoned() || loader.isReset()) return;
listener.onDownloadError(t);
}
}
private final static class DownloadFinishRunnable implements Runnable {
private final GLImageLoader loader;
private final DownloadListener listener;
DownloadFinishRunnable(final GLImageLoader loader, final DownloadListener listener) {
this.loader = loader;
this.listener = listener;
}
@Override
public void run() {
if (listener == null || loader.isAbandoned() || loader.isReset()) return;
listener.onDownloadFinished();
}
}
private final static class DownloadStartRunnable implements Runnable {
private final GLImageLoader loader;
private final DownloadListener listener;
private final long total;
DownloadStartRunnable(final GLImageLoader loader, final DownloadListener listener, final long total) {
this.loader = loader;
this.listener = listener;
this.total = total;
}
@Override
public void run() {
if (listener == null || loader.isAbandoned() || loader.isReset()) return;
listener.onDownloadStart(total);
}
}
private final static class ProgressUpdateRunnable implements Runnable {
private final DownloadListener listener;
private final long current;
ProgressUpdateRunnable(final DownloadListener listener, final long current) {
this.listener = listener;
this.current = current;
}
@Override
public void run() {
if (listener == null) return;
listener.onProgressUpdate(current);
}
}
}

View File

@ -0,0 +1,570 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.gallery3d;
import android.app.ActionBar;
import android.app.ActionBar.OnMenuVisibilityListener;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.SubMenu;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.ProgressBar;
import me.imid.swipebacklayout.lib.SwipeBackLayout.SwipeListener;
import org.mariotaku.gallery3d.ui.GLRoot;
import org.mariotaku.gallery3d.ui.GLRootView;
import org.mariotaku.gallery3d.ui.GLView;
import org.mariotaku.gallery3d.ui.PhotoView;
import org.mariotaku.gallery3d.ui.SynchronizedHandler;
import org.mariotaku.gallery3d.util.GalleryUtils;
import org.mariotaku.gallery3d.util.ThreadPool;
import org.mariotaku.menucomponent.widget.MenuBar;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.support.TwidereSwipeBackActivity;
import org.mariotaku.twidere.util.SaveImageTask;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.Utils;
import java.io.File;
public final class ImageViewerGLActivity extends TwidereSwipeBackActivity implements Constants, PhotoView.Listener,
GLImageLoader.DownloadListener, LoaderManager.LoaderCallbacks<GLImageLoader.Result>, OnMenuVisibilityListener,
SwipeListener, OnMenuItemClickListener {
private final GLView mRootPane = new GLView() {
@Override
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
mPhotoView.layout(0, 0, right - left, bottom - top);
}
};
protected static final int FLAG_HIDE_ACTION_BAR = 1;
protected static final int FLAG_HIDE_STATUS_BAR = 2;
private static final int MSG_HIDE_BARS = 1;
private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
private static final int MSG_UPDATE_ACTION_BAR = 5;
private static final int MSG_UNFREEZE_GLROOT = 6;
private static final int MSG_WANT_BARS = 7;
private static final int MSG_REFRESH_BOTTOM_CONTROLS = 8;
private static final int UNFREEZE_GLROOT_TIMEOUT = 250;
private ActionBar mActionBar;
private GLView mContentPane;
private GLRootView mGLRootView;
private ProgressBar mProgress;
private ImageView mImageViewer;
private MenuBar mMenuBar;
private PhotoView mPhotoView;
private PhotoView.ITileImageAdapter mAdapter;
private Handler mHandler;
protected int mFlags;
private boolean mShowBars = true;
private boolean mActionBarAllowed = true;
private boolean mLoaderInitialized;
private long mContentLength;
private ThreadPool mThreadPool;
private File mImageFile;
public GLRoot getGLRoot() {
return mGLRootView;
}
@Override
public int getThemeResourceId() {
return ThemeUtils.getViewerThemeResource(this);
}
public ThreadPool getThreadPool() {
if (mThreadPool != null) return mThreadPool;
return mThreadPool = new ThreadPool();
}
public void hideProgress() {
mProgress.setVisibility(View.GONE);
mProgress.setProgress(0);
}
@Override
public void onActionBarAllowed(final boolean allowed) {
mActionBarAllowed = allowed;
mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
}
@Override
public void onActionBarWanted() {
mHandler.sendEmptyMessage(MSG_WANT_BARS);
}
@Override
public void onContentChanged() {
super.onContentChanged();
mGLRootView = (GLRootView) findViewById(R.id.gl_root_view);
mImageViewer = (ImageView) findViewById(R.id.image_viewer);
mProgress = (ProgressBar) findViewById(R.id.progress);
mMenuBar = (MenuBar) findViewById(R.id.menu_bar);
}
@Override
public Loader<GLImageLoader.Result> onCreateLoader(final int id, final Bundle args) {
mProgress.setVisibility(View.VISIBLE);
mProgress.setIndeterminate(true);
invalidateOptionsMenu();
final Uri uri = args.getParcelable(EXTRA_URI);
final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1);
return new GLImageLoader(this, this, accountId, uri);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_image_viewer_action_bar, menu);
return true;
}
@Override
public void onCurrentImageUpdated() {
mGLRootView.unfreeze();
}
@Override
public void onDownloadError(final Throwable t) {
mContentLength = 0;
}
@Override
public void onDownloadFinished() {
mContentLength = 0;
}
@Override
public void onDownloadStart(final long total) {
mContentLength = total;
mProgress.setIndeterminate(total <= 0);
mProgress.setMax(total > 0 ? (int) (total / 1024) : 0);
}
@Override
public void onEdgeTouch(final int edgeFlag) {
showBars();
}
@Override
public void onLoaderReset(final Loader<GLImageLoader.Result> loader) {
}
@Override
public void onLoadFinished(final Loader<GLImageLoader.Result> loader, final GLImageLoader.Result data) {
if (data != null && (data.decoder != null || data.bitmap != null)) {
if (data.decoder != null) {
mGLRootView.setVisibility(View.VISIBLE);
mImageViewer.setVisibility(View.GONE);
mAdapter.setData(data.decoder, data.bitmap, data.orientation);
mImageViewer.setImageBitmap(null);
} else if (data.bitmap != null) {
mGLRootView.setVisibility(View.GONE);
mImageViewer.setVisibility(View.VISIBLE);
mImageViewer.setImageBitmap(data.bitmap);
}
mImageFile = data.file;
} else {
mImageFile = null;
if (data != null) {
Utils.showErrorMessage(this, null, data.exception, true);
}
}
mProgress.setVisibility(View.GONE);
mProgress.setProgress(0);
invalidateOptionsMenu();
updateShareIntent();
}
@Override
public boolean onMenuItemClick(final MenuItem item) {
switch (item.getItemId()) {
case MENU_SAVE: {
if (mImageFile != null) {
new SaveImageTask(this, mImageFile).execute();
}
break;
}
case MENU_OPEN_IN_BROWSER: {
final Intent intent = getIntent();
intent.setExtrasClassLoader(getClassLoader());
final Uri uri = intent.getData();
final Uri orig = intent.getParcelableExtra(EXTRA_URI_ORIG);
final Uri uriPreferred = orig != null ? orig : uri;
if (uriPreferred == null) return false;
final String scheme = uriPreferred.getScheme();
if ("http".equals(scheme) || "https".equals(scheme)) {
final Intent open_intent = new Intent(Intent.ACTION_VIEW, uriPreferred);
open_intent.addCategory(Intent.CATEGORY_BROWSABLE);
try {
startActivity(open_intent);
} catch (final ActivityNotFoundException e) {
// Ignore.
}
}
break;
}
default: {
final Intent intent = item.getIntent();
if (intent != null) {
try {
startActivity(intent);
} catch (final ActivityNotFoundException e) {
// Ignore.
}
return true;
}
return false;
}
}
return true;
}
@Override
public void onMenuVisibilityChanged(final boolean isVisible) {
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case MENU_HOME: {
onBackPressed();
break;
}
case MENU_REFRESH: {
loadImage();
break;
}
}
return true;
}
@Override
public void onPictureCenter() {
mPhotoView.setWantPictureCenterCallbacks(false);
}
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
final LoaderManager lm = getSupportLoaderManager();
Utils.setMenuItemAvailability(menu, MENU_REFRESH, !lm.hasRunningLoaders());
return super.onPrepareOptionsMenu(menu);
}
@Override
public void onProgressUpdate(final long downloaded) {
if (mContentLength == 0) {
mProgress.setIndeterminate(true);
return;
}
mProgress.setIndeterminate(false);
mProgress.setProgress((int) (downloaded / 1024));
}
@Override
public void onScrollOverThreshold() {
}
@Override
public void onScrollStateChange(final int state, final float scrollPercent) {
}
@Override
public void onSingleTapUp(final int x, final int y) {
toggleBars();
}
public void showProgress() {
mProgress.setVisibility(View.VISIBLE);
mProgress.setIndeterminate(true);
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image_viewer_gl);
mActionBar = getActionBar();
mActionBar.setDisplayHomeAsUpEnabled(true);
mActionBar.addOnMenuVisibilityListener(this);
mHandler = new MyHandler(this);
mPhotoView = new PhotoView(this);
mPhotoView.setListener(this);
final int bgColor = ThemeUtils.getColorBackgroundCacheHint(this);
final int r = Color.red(bgColor), g = Color.green(bgColor), b = Color.blue(bgColor);
final float[] rootBg = { r / 255f, g / 255f, b / 255f, 1 };
mRootPane.setBackgroundColor(rootBg);
mRootPane.addComponent(mPhotoView);
mAdapter = new PhotoViewAdapter(mPhotoView);
mPhotoView.setModel(mAdapter);
if (savedInstanceState == null) {
loadImage();
}
mMenuBar.setOnMenuItemClickListener(this);
mMenuBar.inflate(R.menu.menu_image_viewer);
mMenuBar.setIsBottomBar(true);
mMenuBar.show();
setSwipeListener(this);
}
@Override
protected void onDestroy() {
mActionBar.removeOnMenuVisibilityListener(this);
super.onDestroy();
mGLRootView.lockRenderThread();
try {
// Remove all pending messages.
mHandler.removeCallbacksAndMessages(null);
} finally {
mGLRootView.unlockRenderThread();
}
}
@Override
protected void onNewIntent(final Intent intent) {
setIntent(intent);
loadImage();
}
@Override
protected void onPause() {
super.onPause();
mGLRootView.onPause();
mGLRootView.lockRenderThread();
try {
mGLRootView.unfreeze();
mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
if (mAdapter != null) {
mAdapter.recycleScreenNail();
}
mPhotoView.pause();
mHandler.removeMessages(MSG_HIDE_BARS);
mHandler.removeMessages(MSG_REFRESH_BOTTOM_CONTROLS);
} finally {
mGLRootView.unlockRenderThread();
}
}
@Override
protected void onResume() {
super.onResume();
mGLRootView.lockRenderThread();
try {
if (mAdapter == null) {
finish();
return;
}
mGLRootView.freeze();
setContentPane(mRootPane);
mPhotoView.resume();
if (!mShowBars) {
hideBars();
}
mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
} finally {
mGLRootView.unlockRenderThread();
}
mGLRootView.onResume();
}
@Override
protected void onSaveInstanceState(final Bundle outState) {
mGLRootView.lockRenderThread();
try {
super.onSaveInstanceState(outState);
} finally {
mGLRootView.unlockRenderThread();
}
}
protected void setContentPane(final GLView content) {
mContentPane = content;
mContentPane.setBackgroundColor(GalleryUtils.intColorToFloatARGBArray(Color.BLACK));
mGLRootView.setContentPane(mContentPane);
}
private boolean canShowBars() {
// No bars if it's not allowed.
if (!mActionBarAllowed) return false;
return true;
}
private void hideBars() {
if (!mShowBars || isSwiping()) return;
mShowBars = false;
mActionBar.hide();
final TranslateAnimation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);
anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
anim.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(final Animation animation) {
mMenuBar.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(final Animation animation) {
}
@Override
public void onAnimationStart(final Animation animation) {
}
});
mMenuBar.startAnimation(anim);
mHandler.removeMessages(MSG_HIDE_BARS);
}
private void loadImage() {
getSupportLoaderManager().destroyLoader(0);
final Intent intent = getIntent();
final Uri uri = intent.getData();
final long accountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1);
if (uri == null) {
finish();
return;
}
final Bundle args = new Bundle();
args.putParcelable(EXTRA_URI, uri);
args.putLong(EXTRA_ACCOUNT_ID, accountId);
if (!mLoaderInitialized) {
getSupportLoaderManager().initLoader(0, args, this);
mLoaderInitialized = true;
} else {
getSupportLoaderManager().restartLoader(0, args, this);
}
}
private void showBars() {
if (mShowBars) return;
mShowBars = true;
mActionBar.show();
mMenuBar.setVisibility(View.VISIBLE);
final TranslateAnimation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0);
anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
mMenuBar.startAnimation(anim);
}
private void toggleBars() {
if (mShowBars) {
hideBars();
} else {
if (canShowBars()) {
showBars();
}
}
}
private void updateBars() {
if (!canShowBars()) {
hideBars();
}
}
private void wantBars() {
if (canShowBars()) {
showBars();
}
}
void updateShareIntent() {
final MenuItem item = mMenuBar.getMenu().findItem(MENU_SHARE);
if (item == null || !item.hasSubMenu()) return;
final SubMenu subMenu = item.getSubMenu();
subMenu.clear();
final Intent intent = getIntent();
final Uri uri = intent.getData();
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
if (mImageFile != null && mImageFile.exists()) {
shareIntent.setType("image/*");
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(mImageFile));
} else {
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, uri.toString());
}
Utils.addIntentToMenu(this, subMenu, shareIntent);
}
private static class MyHandler extends SynchronizedHandler {
ImageViewerGLActivity activity;
private MyHandler(final ImageViewerGLActivity activity) {
super(activity.getGLRoot());
this.activity = activity;
}
@Override
public void handleMessage(final Message message) {
switch (message.what) {
case MSG_HIDE_BARS: {
activity.hideBars();
break;
}
case MSG_REFRESH_BOTTOM_CONTROLS: {
break;
}
case MSG_ON_FULL_SCREEN_CHANGED: {
break;
}
case MSG_UPDATE_ACTION_BAR: {
activity.updateBars();
break;
}
case MSG_WANT_BARS: {
activity.wantBars();
break;
}
case MSG_UNFREEZE_GLROOT: {
mGLRoot.unfreeze();
break;
}
}
}
}
}

View File

@ -0,0 +1,241 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.gallery3d;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;
import org.mariotaku.gallery3d.ui.BitmapScreenNail;
import org.mariotaku.gallery3d.ui.PhotoView;
import org.mariotaku.gallery3d.ui.ScreenNail;
import org.mariotaku.gallery3d.util.ApiHelper;
import org.mariotaku.gallery3d.util.BitmapPool;
import org.mariotaku.gallery3d.util.GalleryUtils;
public class PhotoViewAdapter implements PhotoView.ITileImageAdapter {
private static final String TAG = "PhotoViewAdapter";
protected ScreenNail mScreenNail;
protected BitmapRegionDecoder mRegionDecoder;
protected int mImageWidth;
protected int mImageHeight;
protected int mLevelCount;
private final PhotoView mPhotoView;
private BitmapScreenNail mBitmapScreenNail;
private int mImageRotation;
public PhotoViewAdapter(final PhotoView view) {
mPhotoView = view;
}
@Override
public int getImageHeight() {
return mImageHeight;
}
@Override
public int getImageRotation() {
return mImageRotation;
}
@Override
public int getImageWidth() {
return mImageWidth;
}
@Override
public int getLevelCount() {
return mLevelCount;
}
@Override
public ScreenNail getScreenNail() {
return mScreenNail;
}
// Gets a sub image on a rectangle of the current photo. For example,
// getTile(1, 50, 50, 100, 3, pool) means to get the region located
// at (50, 50) with sample level 1 (ie, down sampled by 2^1) and the
// target tile size (after sampling) 100 with border 3.
//
// From this spec, we can infer the actual tile size to be
// 100 + 3x2 = 106, and the size of the region to be extracted from the
// photo to be 200 with border 6.
//
// As a result, we should decode region (50-6, 50-6, 250+6, 250+6) or
// (44, 44, 256, 256) from the original photo and down sample it to 106.
@Override
public Bitmap getTile(final int level, final int x, final int y, final int tileSize, final int borderSize,
final BitmapPool pool) {
if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER)
return getTileWithoutReusingBitmap(level, x, y, tileSize, borderSize);
final int b = borderSize << level;
final int t = tileSize << level;
final Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b);
boolean needClear;
BitmapRegionDecoder regionDecoder = null;
synchronized (this) {
regionDecoder = mRegionDecoder;
if (regionDecoder == null) return null;
// We need to clear a reused bitmap, if wantRegion is not fully
// within the image.
needClear = !new Rect(0, 0, mImageWidth, mImageHeight).contains(wantRegion);
}
Bitmap bitmap = pool == null ? null : pool.getBitmap();
if (bitmap != null) {
if (needClear) {
bitmap.eraseColor(0);
}
} else {
final int s = tileSize + 2 * borderSize;
bitmap = Bitmap.createBitmap(s, s, Config.RGB_565);
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.RGB_565;
options.inPreferQualityOverSpeed = true;
options.inSampleSize = 1 << level;
options.inBitmap = bitmap;
try {
// In CropImage, we may call the decodeRegion() concurrently.
synchronized (regionDecoder) {
bitmap = regionDecoder.decodeRegion(wantRegion, options);
}
} finally {
if (options.inBitmap != bitmap && options.inBitmap != null) {
if (pool != null) {
pool.recycle(options.inBitmap);
}
options.inBitmap = null;
}
}
if (bitmap == null) {
Log.w(TAG, "fail in decoding region");
}
return bitmap;
}
@Override
public void recycleScreenNail() {
if (mBitmapScreenNail != null) {
mBitmapScreenNail.recycle();
mBitmapScreenNail = null;
}
}
@Override
public boolean setData(final BitmapRegionDecoder decoder, final Bitmap bitmap, final int oroentation) {
try {
if (decoder != null) {
setScreenNail(bitmap, decoder.getWidth(), decoder.getHeight());
} else {
if (bitmap == null) return false;
setScreenNail(bitmap, bitmap.getWidth(), bitmap.getHeight());
}
setRegionDecoder(decoder);
mPhotoView.notifyImageChange();
return true;
} catch (final Throwable t) {
Log.w(TAG, "fail to decode large", t);
return false;
}
}
private int calculateLevelCount() {
return Math.max(0, GalleryUtils.ceilLog2((float) mImageWidth / mScreenNail.getWidth()));
}
private Bitmap getTileWithoutReusingBitmap(final int level, final int x, final int y, final int tileSize,
final int borderSize) {
final int b = borderSize << level;
final int t = tileSize << level;
final Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b);
BitmapRegionDecoder regionDecoder;
Rect overlapRegion;
synchronized (this) {
regionDecoder = mRegionDecoder;
if (regionDecoder == null) return null;
overlapRegion = new Rect(0, 0, mImageWidth, mImageHeight);
GalleryUtils.assertTrue(overlapRegion.intersect(wantRegion));
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.RGB_565;
options.inPreferQualityOverSpeed = true;
options.inSampleSize = 1 << level;
Bitmap bitmap = null;
// In CropImage, we may call the decodeRegion() concurrently.
synchronized (regionDecoder) {
bitmap = regionDecoder.decodeRegion(overlapRegion, options);
}
if (bitmap == null) {
Log.w(TAG, "fail in decoding region");
}
if (wantRegion.equals(overlapRegion)) return bitmap;
final int s = tileSize + 2 * borderSize;
final Bitmap result = Bitmap.createBitmap(s, s, Config.RGB_565);
final Canvas canvas = new Canvas(result);
canvas.drawBitmap(bitmap, overlapRegion.left - wantRegion.left >> level,
overlapRegion.top - wantRegion.top >> level, null);
return result;
}
private synchronized void setRegionDecoder(final BitmapRegionDecoder decoder) {
mRegionDecoder = decoder;
if (decoder == null) return;
mImageWidth = decoder.getWidth();
mImageHeight = decoder.getHeight();
mLevelCount = calculateLevelCount();
}
private void setScreenNail(final Bitmap bitmap, final int width, final int height) {
mBitmapScreenNail = new BitmapScreenNail(bitmap);
setScreenNail(mBitmapScreenNail, width, height);
}
// Caller is responsible to recycle the ScreenNail
private synchronized void setScreenNail(final ScreenNail screenNail, final int width, final int height) {
mScreenNail = GalleryUtils.checkNotNull(screenNail);
mImageWidth = width;
mImageHeight = height;
mRegionDecoder = null;
mLevelCount = 0;
}
}

Some files were not shown because too many files have changed in this diff Show More