├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── app ├── src │ └── main │ │ ├── res │ │ ├── drawable-xhdpi │ │ │ └── history.png │ │ ├── values │ │ │ ├── styles.xml │ │ │ ├── colors.xml │ │ │ ├── attrs_civ.xml │ │ │ ├── strings.xml │ │ │ └── dimens.xml │ │ ├── layout │ │ │ ├── row_view.xml │ │ │ ├── history_view.xml │ │ │ ├── history_row.xml │ │ │ ├── image_card.xml │ │ │ ├── activity_layout.xml │ │ │ └── search_view.xml │ │ ├── anim │ │ │ ├── fade_out.xml │ │ │ ├── fade_in.xml │ │ │ ├── slide_in_from_right.xml │ │ │ ├── slide_out_to_left.xml │ │ │ ├── slide_out_to_right.xml │ │ │ └── slide_in_from_left.xml │ │ ├── drawable │ │ │ ├── white_view_with_border_clicked.xml │ │ │ ├── grey_with_border.xml │ │ │ └── white_with_border.xml │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ └── menu │ │ │ └── menu_main.xml │ │ ├── java │ │ └── com │ │ │ └── rx │ │ │ └── demo │ │ │ ├── ui │ │ │ ├── view │ │ │ │ ├── presenter │ │ │ │ │ ├── IViewPresenter.java │ │ │ │ │ └── ImageSearchPresenter.java │ │ │ │ ├── SquareDraweeView.java │ │ │ │ ├── ImageCardView.java │ │ │ │ ├── HistoryView.java │ │ │ │ └── SearchView.java │ │ │ ├── activity │ │ │ │ ├── BaseActivity.java │ │ │ │ └── SearchActivity.java │ │ │ └── animation │ │ │ │ ├── FlipAnimation.java │ │ │ │ └── AnimationFactory.java │ │ │ ├── di │ │ │ ├── annotation │ │ │ │ ├── ImageViewBus.java │ │ │ │ └── HistoryViewBus.java │ │ │ ├── DataModule.java │ │ │ └── UiModule.java │ │ │ ├── rest │ │ │ └── ImagesApi.java │ │ │ ├── model │ │ │ ├── ImageRequest.java │ │ │ ├── Page.java │ │ │ ├── ResponseData.java │ │ │ ├── ImageResponse.java │ │ │ ├── Cursor.java │ │ │ └── Result.java │ │ │ ├── util │ │ │ └── SubscriptionManager.java │ │ │ ├── DemoApplication.java │ │ │ └── dao │ │ │ ├── ImageDao.java │ │ │ └── RxDao.java │ │ └── AndroidManifest.xml ├── .gitignore ├── build.gradle └── app.iml ├── README.md ├── .gitignore ├── gradle.properties ├── import-summary.txt ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digitalbuddha/RxStarter/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digitalbuddha/RxStarter/HEAD/app/src/main/res/drawable-xhdpi/history.png -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jan 25 18:05:52 EST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/row_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/view/presenter/IViewPresenter.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.ui.view.presenter; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by Nakhimovich on 4/10/15. 7 | */ 8 | public interface IViewPresenter { 9 | void takeView(View view); 10 | 11 | void dropView(); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffff0000 4 | #ffffffff 5 | #f5f5f5 6 | #ffcccccc 7 | #ff111111 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/white_view_with_border_clicked.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/di/annotation/ImageViewBus.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.di.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | 5 | import javax.inject.Qualifier; 6 | 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | /** 10 | * Created by Nakhimovich on 4/11/15. 11 | */ 12 | @Qualifier 13 | @Retention(RUNTIME) 14 | public @interface ImageViewBus { 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/di/annotation/HistoryViewBus.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.di.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | 5 | import javax.inject.Qualifier; 6 | 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | /** 10 | * Created by Nakhimovich on 4/10/15. 11 | */ 12 | @Qualifier 13 | @Retention(RUNTIME) 14 | public @interface HistoryViewBus { 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/history_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 |
62 | * 63 | */ 64 | public void addRow() { 65 | ViewGroup row = getLastResultsRow(); 66 | while (row.getChildCount() < 3) { 67 | inflate(getContext(), R.layout.image_card, row); 68 | } 69 | presenter.drawNewRowIfNeeded(); 70 | } 71 | 72 | /** 73 | * if less than 3 images in last row return it 74 | * else return a newly added linear layout 75 | * 76 | * @return ViewGroup with 0-2 images 77 | */ 78 | private ViewGroup getLastResultsRow() { 79 | ViewGroup bottomRow = (ViewGroup) cardsLayout.getChildAt(cardsLayout.getChildCount() - 1); 80 | if (bottomRow == null || bottomRow.getChildCount() == 3) { 81 | inflate(getContext(), R.layout.row_view, cardsLayout); 82 | bottomRow = (ViewGroup) cardsLayout.getChildAt(cardsLayout.getChildCount() - 1); 83 | } 84 | return bottomRow; 85 | } 86 | 87 | 88 | /** 89 | * detach from presenter 90 | */ 91 | @Override 92 | protected void onDetachedFromWindow() { 93 | super.onDetachedFromWindow(); 94 | presenter.dropView(); 95 | } 96 | 97 | public EditText getSearchView() { 98 | return getSearch(); 99 | } 100 | 101 | public EditText getSearch() { 102 | return search; 103 | } 104 | 105 | public void updateSearchView(String searchTerm) { 106 | search.setText(searchTerm); 107 | presenter.clearResults(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/dao/RxDao.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.dao; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import rx.Observable; 10 | import rx.subjects.PublishSubject; 11 | 12 | import static rx.Observable.create; 13 | import static rx.Observable.just; 14 | 15 | 16 | //T = request type, V = response type 17 | public abstract class RxDao { 18 | private final Map cachedResponses; 19 | protected Map> inFlightRequests = new HashMap<>(); 20 | protected PublishSubject onNextObservable = PublishSubject.create(); 21 | protected PublishSubject updateObservable = PublishSubject.create(); 22 | private Gson gson = new Gson(); 23 | 24 | public RxDao() { 25 | cachedResponses = Collections.synchronizedMap(new HashMap<>()); 26 | inFlightRequests = Collections.synchronizedMap(new HashMap<>()); 27 | } 28 | 29 | public Observable fresh(final T request) { 30 | return isInFlightNetwork(request) ? inFlightResponse(request) : response(request); 31 | } 32 | 33 | public Observable cached(final T request) { 34 | return just(getCachedValue(request)); 35 | } 36 | 37 | public Observable all(final T request) { 38 | return fresh(request).startWith(cached(request)); 39 | } 40 | 41 | public Observable get(final T request) { 42 | V cachedValue = getCachedValue(request); 43 | return cachedValue == null ? fresh(request) : cached(request); 44 | } 45 | 46 | boolean isInFlightNetwork(T request) { 47 | return request != null && inFlightRequests.containsKey(json(request)); 48 | } 49 | 50 | public abstract V load(T request) throws Exception; 51 | 52 | private void loadResponse(final T request) throws Exception { 53 | V result = load(request); 54 | cachedResponses.put(json(request), result); 55 | } 56 | 57 | private String json(T request) { 58 | return gson.toJson(request); 59 | } 60 | 61 | protected Observable response(final T request) { 62 | final Observable response = create(subscriber -> { 63 | try { 64 | subscriber.onStart(); 65 | loadResponse(request); 66 | subscriber.onNext(getCachedValue(request)); 67 | subscriber.onCompleted(); 68 | } catch (Exception e) { 69 | subscriber.onError(e); 70 | } 71 | }); 72 | return registerResponse(request, response); 73 | } 74 | 75 | Observable inFlightResponse(T request) { 76 | return inFlightRequests.get(json(request)); 77 | } 78 | 79 | private V getCachedValue(T request) { 80 | V v = cachedResponses.get(json(request)); 81 | if (v != null) { 82 | onNextObservable.onNext(v); 83 | } 84 | //TODO: make a defensive copy of the cached value in case V is mutable 85 | return v; 86 | } 87 | 88 | protected Observable registerResponse(final T request, final Observable response) { 89 | return response 90 | .doOnSubscribe(() -> inFlightRequests.put(json(request), response)) 91 | .doOnCompleted(() -> inFlightRequests.remove(json(request))).doOnNext(updateObservable::onNext); 92 | } 93 | 94 | 95 | public Observable onNextObservable() { 96 | return onNextObservable.asObservable(); 97 | } 98 | 99 | public Observable onUpdateObservable() { 100 | return updateObservable.asObservable(); 101 | } 102 | } 103 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/view/presenter/ImageSearchPresenter.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.ui.view.presenter; 2 | 3 | import android.graphics.Rect; 4 | import android.util.Log; 5 | import android.view.View; 6 | 7 | import com.rx.demo.dao.ImageDao; 8 | import com.rx.demo.di.annotation.HistoryViewBus; 9 | import com.rx.demo.di.annotation.ImageViewBus; 10 | import com.rx.demo.model.ImageRequest; 11 | import com.rx.demo.model.Result; 12 | import com.rx.demo.ui.view.SearchView; 13 | import com.rx.demo.util.SubscriptionManager; 14 | 15 | import java.util.Queue; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import javax.inject.Inject; 19 | import javax.inject.Singleton; 20 | 21 | import rx.Observable; 22 | import rx.android.schedulers.AndroidSchedulers; 23 | import rx.android.widget.WidgetObservable; 24 | import rx.schedulers.Schedulers; 25 | import rx.subjects.PublishSubject; 26 | 27 | @Singleton 28 | public class ImageSearchPresenter implements IViewPresenter { 29 | @Inject 30 | Queue que; 31 | 32 | @Inject 33 | ImageDao dao; 34 | 35 | @Inject 36 | SubscriptionManager subs; 37 | 38 | @Inject 39 | @ImageViewBus 40 | PublishSubject imagesBus; 41 | 42 | @Inject 43 | @HistoryViewBus 44 | PublishSubject historyViewBus; 45 | 46 | private SearchView view; 47 | 48 | /** 49 | * Binds to view 50 | * Initializes subscriptions 51 | * Adds an on Scroll listner to load more images 52 | * 53 | * @param view view with search term and image results 54 | */ 55 | @Override 56 | public void takeView(View view) { 57 | if (view == null) return; 58 | this.view = (SearchView) view; 59 | initSubs(); 60 | addOnScrollListener(); 61 | } 62 | 63 | /** 64 | * subscribes to search view 65 | * transforms the onTextChange event into the search String 66 | * filters any blank searches 67 | * emits only the last emitted value when no search for 1 second 68 | * on a new valid search occurs 69 | * clear screen and queue 70 | * emit new search terms to history view bus 71 | * 72 | * @return Observable containing the search term 73 | */ 74 | private Observable searchTermObservable() { 75 | return WidgetObservable.text(view.getSearchView()) 76 | .map(onTextChangeEvent -> onTextChangeEvent.text().toString()) 77 | .filter(searchTerm -> searchTerm.length() > 0) 78 | .debounce(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) 79 | .doOnNext(results -> clearResults()) 80 | .doOnNext(getHistoryViewBus()::onNext); 81 | } 82 | 83 | /** 84 | * on search term change request images 85 | * subscribe with imagesBus 86 | * 87 | * images become available -> emit to imagesBus 88 | * 89 | * on emit to imagesBus -> try to add new rows 90 | * 91 | * NOTE: imagesBus drops events followed by another event within 300ms 92 | */ 93 | private void initSubs() { 94 | searchTermObservable() 95 | .observeOn(Schedulers.io()) 96 | .flatMap(s -> dao.fetchImageResults(new ImageRequest(s))) 97 | .doOnNext(que::add) 98 | .doOnError(throwable -> { 99 | throw new RuntimeException(throwable); 100 | }) 101 | .subscribe(imagesBus); 102 | 103 | imagesBus.debounce(50, TimeUnit.MILLISECONDS, Schedulers.computation()) 104 | .filter(o -> isLastRowVisible() && que.size() > 3) 105 | .observeOn(AndroidSchedulers.mainThread()) 106 | .subscribe(o -> displayNextRow()); 107 | } 108 | 109 | 110 | /** 111 | * If scrolled to last row, emit new item to imagesBus 112 | */ 113 | private void addOnScrollListener() { 114 | view.cardsLayout.getViewTreeObserver().addOnScrollChangedListener(() -> { 115 | imagesBus.onNext(que.peek()); 116 | }); 117 | } 118 | 119 | /** 120 | * gets images from queue and binds to newly created views 121 | */ 122 | public void displayNextRow() { 123 | Log.e(this.getClass().getSimpleName(), "last row is visible on screen, load next rows"); 124 | view.addRow(); 125 | 126 | } 127 | 128 | public void drawNewRowIfNeeded() { 129 | imagesBus.onNext(que.peek()); 130 | } 131 | 132 | 133 | /** 134 | * clear queue and view of previous results 135 | */ 136 | public void clearResults() { 137 | view.cardsLayout.removeAllViews(); 138 | que.clear(); 139 | } 140 | 141 | 142 | /** 143 | * determines whether the last row of images is visible on the screen 144 | * 145 | * @return boolean 146 | */ 147 | public boolean isLastRowVisible() { 148 | if (que.size() == 0 || view == null) { 149 | return false; 150 | } 151 | Rect scrollBounds = new Rect(); 152 | view.cardsLayout.getHitRect(scrollBounds); 153 | View lastRow = view.cardsLayout.getChildAt(view.cardsLayout.getChildCount() - 1); 154 | return lastRow == null || lastRow.getLocalVisibleRect(scrollBounds); 155 | } 156 | 157 | /** 158 | * unsubscribe when view is destroyed 159 | */ 160 | @Override 161 | public void dropView() { 162 | view = null; 163 | subs.unsubscribeAll(); 164 | } 165 | 166 | public void changeSearchTerm(String s) { 167 | view.updateSearchView(s); 168 | } 169 | 170 | public PublishSubject getHistoryViewBus() { 171 | return historyViewBus; 172 | } 173 | 174 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/model/Result.java: -------------------------------------------------------------------------------- 1 | 2 | package com.rx.demo.model; 3 | 4 | public class Result { 5 | 6 | private String GsearchResultClass; 7 | private String width; 8 | private String height; 9 | private String imageId; 10 | private String tbWidth; 11 | private String tbHeight; 12 | private String unescapedUrl; 13 | private String url; 14 | private String visibleUrl; 15 | private String title; 16 | private String titleNoFormatting; 17 | private String originalContextUrl; 18 | private String content; 19 | private String contentNoFormatting; 20 | private String tbUrl; 21 | 22 | /** 23 | * 24 | * @return 25 | * The GsearchResultClass 26 | */ 27 | public String getGsearchResultClass() { 28 | return GsearchResultClass; 29 | } 30 | 31 | /** 32 | * 33 | * @param GsearchResultClass 34 | * The GsearchResultClass 35 | */ 36 | public void setGsearchResultClass(String GsearchResultClass) { 37 | this.GsearchResultClass = GsearchResultClass; 38 | } 39 | 40 | /** 41 | * 42 | * @return 43 | * The width 44 | */ 45 | public String getWidth() { 46 | return width; 47 | } 48 | 49 | /** 50 | * 51 | * @param width 52 | * The width 53 | */ 54 | public void setWidth(String width) { 55 | this.width = width; 56 | } 57 | 58 | /** 59 | * 60 | * @return 61 | * The height 62 | */ 63 | public String getHeight() { 64 | return height; 65 | } 66 | 67 | /** 68 | * 69 | * @param height 70 | * The height 71 | */ 72 | public void setHeight(String height) { 73 | this.height = height; 74 | } 75 | 76 | /** 77 | * 78 | * @return 79 | * The imageId 80 | */ 81 | public String getImageId() { 82 | return imageId; 83 | } 84 | 85 | /** 86 | * 87 | * @param imageId 88 | * The imageId 89 | */ 90 | public void setImageId(String imageId) { 91 | this.imageId = imageId; 92 | } 93 | 94 | /** 95 | * 96 | * @return 97 | * The tbWidth 98 | */ 99 | public String getTbWidth() { 100 | return tbWidth; 101 | } 102 | 103 | /** 104 | * 105 | * @param tbWidth 106 | * The tbWidth 107 | */ 108 | public void setTbWidth(String tbWidth) { 109 | this.tbWidth = tbWidth; 110 | } 111 | 112 | /** 113 | * 114 | * @return 115 | * The tbHeight 116 | */ 117 | public String getTbHeight() { 118 | return tbHeight; 119 | } 120 | 121 | /** 122 | * 123 | * @param tbHeight 124 | * The tbHeight 125 | */ 126 | public void setTbHeight(String tbHeight) { 127 | this.tbHeight = tbHeight; 128 | } 129 | 130 | /** 131 | * 132 | * @return 133 | * The unescapedUrl 134 | */ 135 | public String getUnescapedUrl() { 136 | return unescapedUrl; 137 | } 138 | 139 | /** 140 | * 141 | * @param unescapedUrl 142 | * The unescapedUrl 143 | */ 144 | public void setUnescapedUrl(String unescapedUrl) { 145 | this.unescapedUrl = unescapedUrl; 146 | } 147 | 148 | /** 149 | * 150 | * @return 151 | * The url 152 | */ 153 | public String getUrl() { 154 | return url; 155 | } 156 | 157 | /** 158 | * 159 | * @param url 160 | * The url 161 | */ 162 | public void setUrl(String url) { 163 | this.url = url; 164 | } 165 | 166 | /** 167 | * 168 | * @return 169 | * The visibleUrl 170 | */ 171 | public String getVisibleUrl() { 172 | return visibleUrl; 173 | } 174 | 175 | /** 176 | * 177 | * @param visibleUrl 178 | * The visibleUrl 179 | */ 180 | public void setVisibleUrl(String visibleUrl) { 181 | this.visibleUrl = visibleUrl; 182 | } 183 | 184 | /** 185 | * 186 | * @return 187 | * The title 188 | */ 189 | public String getTitle() { 190 | return title; 191 | } 192 | 193 | /** 194 | * 195 | * @param title 196 | * The title 197 | */ 198 | public void setTitle(String title) { 199 | this.title = title; 200 | } 201 | 202 | /** 203 | * 204 | * @return 205 | * The titleNoFormatting 206 | */ 207 | public String getTitleNoFormatting() { 208 | return titleNoFormatting; 209 | } 210 | 211 | /** 212 | * 213 | * @param titleNoFormatting 214 | * The titleNoFormatting 215 | */ 216 | public void setTitleNoFormatting(String titleNoFormatting) { 217 | this.titleNoFormatting = titleNoFormatting; 218 | } 219 | 220 | /** 221 | * 222 | * @return 223 | * The originalContextUrl 224 | */ 225 | public String getOriginalContextUrl() { 226 | return originalContextUrl; 227 | } 228 | 229 | /** 230 | * 231 | * @param originalContextUrl 232 | * The originalContextUrl 233 | */ 234 | public void setOriginalContextUrl(String originalContextUrl) { 235 | this.originalContextUrl = originalContextUrl; 236 | } 237 | 238 | /** 239 | * 240 | * @return 241 | * The content 242 | */ 243 | public String getContent() { 244 | return content; 245 | } 246 | 247 | /** 248 | * 249 | * @param content 250 | * The content 251 | */ 252 | public void setContent(String content) { 253 | this.content = content; 254 | } 255 | 256 | /** 257 | * 258 | * @return 259 | * The contentNoFormatting 260 | */ 261 | public String getContentNoFormatting() { 262 | return contentNoFormatting; 263 | } 264 | 265 | /** 266 | * 267 | * @param contentNoFormatting 268 | * The contentNoFormatting 269 | */ 270 | public void setContentNoFormatting(String contentNoFormatting) { 271 | this.contentNoFormatting = contentNoFormatting; 272 | } 273 | 274 | /** 275 | * 276 | * @return 277 | * The tbUrl 278 | */ 279 | public String getTbUrl() { 280 | return tbUrl; 281 | } 282 | 283 | /** 284 | * 285 | * @param tbUrl 286 | * The tbUrl 287 | */ 288 | public void setTbUrl(String tbUrl) { 289 | this.tbUrl = tbUrl; 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/animation/FlipAnimation.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012 Ephraim Tekle genzeb@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | * associated documentation files (the "Software"), to deal in the Software without restriction, including 6 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 8 | * following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all copies or substantial 11 | * portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 14 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 15 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | * 19 | * @author Ephraim A. Tekle 20 | * 21 | */ 22 | package com.rx.demo.ui.animation; 23 | 24 | import android.view.animation.Animation; 25 | import android.graphics.Camera; 26 | import android.graphics.Matrix; 27 | import android.view.animation.Transformation; 28 | 29 | /** 30 | * This class extends Animation to support a 3D flip view transition animation. Two instances of this class is 31 | * required: one for the "from" view and another for the "to" view. 32 | * 33 | * NOTE: use {@link AnimationFactory} to use this class. 34 | * 35 | * @author Ephraim A. Tekle 36 | * 37 | */ 38 | public class FlipAnimation extends Animation { 39 | public static final int ROTATION_X = 0; 40 | public static final int ROTATION_Y = 1; 41 | private final float mFromDegrees; 42 | private final float mToDegrees; 43 | private final float mCenterX; 44 | private final float mCenterY; 45 | private Camera mCamera; 46 | private int mDirection; 47 | 48 | private final ScaleUpDownEnum scaleType; 49 | 50 | /** 51 | * How much to scale up/down. The default scale of 75% of full size seems optimal based on testing. Feel free to experiment away, however. 52 | */ 53 | public static final float SCALE_DEFAULT = 0.75f; 54 | 55 | private float scale; 56 | 57 | /** 58 | * Constructs a new {@code FlipAnimation} object.Two {@code FlipAnimation} objects are needed for a complete transition b/n two views. 59 | * 60 | * @param fromDegrees the start angle in degrees for a rotation along the y-axis, i.e. in-and-out of the screen, i.e. 3D flip. This should really be multiple of 90 degrees. 61 | * @param toDegrees the end angle in degrees for a rotation along the y-axis, i.e. in-and-out of the screen, i.e. 3D flip. This should really be multiple of 90 degrees. 62 | * @param centerX the x-axis value of the center of rotation 63 | * @param centerY the y-axis value of the center of rotation 64 | * @param scale to get a 3D effect, the transition views need to be zoomed (scaled). This value must be b/n (0,1) or else the default scale {@link #SCALE_DEFAULT} is used. 65 | * @param scaleType flip view transition is broken down into two: the zoom-out of the "from" view and the zoom-in of the "to" view. This parameter is used to determine which is being done. See {@link com.rx.demo.ui.animation.FlipAnimation.ScaleUpDownEnum}. 66 | */ 67 | public FlipAnimation(float fromDegrees, float toDegrees, float centerX, float centerY, float scale, ScaleUpDownEnum scaleType) { 68 | mFromDegrees = fromDegrees; 69 | mToDegrees = toDegrees; 70 | mCenterX = centerX; 71 | mCenterY = centerY; 72 | this.scale = (scale<=0||scale>=1)?SCALE_DEFAULT:scale; 73 | this.scaleType = scaleType==null?ScaleUpDownEnum.SCALE_CYCLE:scaleType; 74 | mDirection = ROTATION_Y; 75 | } 76 | 77 | @Override 78 | public void initialize(int width, int height, int parentWidth, int parentHeight) { 79 | super.initialize(width, height, parentWidth, parentHeight); 80 | mCamera = new Camera(); 81 | } 82 | 83 | @Override 84 | protected void applyTransformation(float interpolatedTime, Transformation t) { 85 | final float fromDegrees = mFromDegrees; 86 | float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); 87 | 88 | final float centerX = mCenterX; 89 | final float centerY = mCenterY; 90 | final Camera camera = mCamera; 91 | 92 | final Matrix matrix = t.getMatrix(); 93 | 94 | camera.save(); 95 | 96 | if (mDirection == ROTATION_X) 97 | camera.rotateX(degrees); 98 | else 99 | camera.rotateY(degrees); 100 | 101 | camera.getMatrix(matrix); 102 | camera.restore(); 103 | 104 | matrix.preTranslate(-centerX, -centerY); 105 | matrix.postTranslate(centerX, centerY); 106 | 107 | matrix.preScale(scaleType.getScale(scale, interpolatedTime), scaleType.getScale(scale, interpolatedTime), centerX, centerY); 108 | 109 | } 110 | 111 | /** 112 | * Get the current direction, it can be {@link #ROTATION_X} or {#ROTATION_Y} 113 | * @param direction 114 | */ 115 | public void setDirection(int direction) { 116 | mDirection = direction; 117 | } 118 | 119 | /** 120 | * This enumeration is used to determine the zoom (or scale) behavior of a {@link com.rx.demo.ui.animation.FlipAnimation}. 121 | * 122 | * @author Ephraim A. Tekle 123 | * 124 | */ 125 | public static enum ScaleUpDownEnum { 126 | /** 127 | * The view will be scaled up from the scale value until it's at 100% zoom level (i.e. no zoom). 128 | */ 129 | SCALE_UP, 130 | /** 131 | * The view will be scaled down starting at no zoom (100% zoom level) until it's at a specified zoom level. 132 | */ 133 | SCALE_DOWN, 134 | /** 135 | * The view will cycle through a zoom down and then zoom up. 136 | */ 137 | SCALE_CYCLE, 138 | /** 139 | * No zoom effect is applied. 140 | */ 141 | SCALE_NONE; 142 | 143 | /** 144 | * The intermittent zoom level given the current or desired maximum zoom level for the specified iteration 145 | * 146 | * @param max the maximum desired or current zoom level 147 | * @param iter the iteration (from 0..1). 148 | * @return the current zoom level 149 | */ 150 | public float getScale(float max, float iter) { 151 | switch(this) { 152 | case SCALE_UP: 153 | return max + (1-max)*iter; 154 | 155 | case SCALE_DOWN: 156 | return 1 - (1-max)*iter; 157 | 158 | case SCALE_CYCLE: { 159 | final boolean halfWay = (iter > 0.5); 160 | 161 | if (halfWay) { 162 | return max + (1-max)*(iter-0.5f)*2; 163 | } else { 164 | return 1 - (1-max)*(iter*2); 165 | } 166 | } 167 | 168 | default: 169 | return 1; 170 | } 171 | } 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/animation/AnimationFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012 Ephraim Tekle genzeb@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | * associated documentation files (the "Software"), to deal in the Software without restriction, including 6 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 8 | * following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all copies or substantial 11 | * portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 14 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 15 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | * 19 | * @author Ephraim A. Tekle 20 | * 21 | */ 22 | package com.rx.demo.ui.animation; 23 | 24 | import android.view.View; 25 | import android.view.animation.AccelerateInterpolator; 26 | import android.view.animation.AlphaAnimation; 27 | import android.view.animation.Animation; 28 | import android.view.animation.AnimationSet; 29 | import android.view.animation.DecelerateInterpolator; 30 | import android.view.animation.Interpolator; 31 | import android.view.animation.TranslateAnimation; 32 | import android.view.animation.Animation.AnimationListener; 33 | import android.widget.ViewAnimator; 34 | 35 | /** 36 | * This class contains methods for creating {@link android.view.animation.Animation} objects for some of the most common animation, including a 3D flip animation, {@link FlipAnimation}. 37 | * Furthermore, utility methods are provided for initiating fade-in-then-out and flip animations. 38 | * 39 | * @author Ephraim A. Tekle 40 | * 41 | */ 42 | public class AnimationFactory { 43 | 44 | private static final int DEFAULT_FLIP_TRANSITION_DURATION = 500; 45 | 46 | /** 47 | * The {@code FlipDirection} enumeration defines the most typical flip view transitions: left-to-right and right-to-left. {@code FlipDirection} is used during the creation of {@link FlipAnimation} animations. 48 | * 49 | * @author Ephraim A. Tekle 50 | * 51 | */ 52 | public static enum FlipDirection { 53 | LEFT_RIGHT, 54 | RIGHT_LEFT, 55 | TOP_BOTTOM, 56 | BOTTOM_TOP; 57 | 58 | public float getStartDegreeForFirstView() { 59 | return 0; 60 | } 61 | 62 | public float getStartDegreeForSecondView() { 63 | switch(this) { 64 | case LEFT_RIGHT: 65 | case TOP_BOTTOM: 66 | return -90; 67 | case RIGHT_LEFT: 68 | case BOTTOM_TOP: 69 | return 90; 70 | default: 71 | return 0; 72 | } 73 | } 74 | 75 | public float getEndDegreeForFirstView() { 76 | switch(this) { 77 | case LEFT_RIGHT: 78 | case TOP_BOTTOM: 79 | return 90; 80 | case RIGHT_LEFT: 81 | case BOTTOM_TOP: 82 | return -90; 83 | default: 84 | return 0; 85 | } 86 | } 87 | 88 | public float getEndDegreeForSecondView() { 89 | return 0; 90 | } 91 | 92 | public FlipDirection theOtherDirection() { 93 | switch(this) { 94 | case LEFT_RIGHT: 95 | return RIGHT_LEFT; 96 | case TOP_BOTTOM: 97 | return BOTTOM_TOP; 98 | case RIGHT_LEFT: 99 | return LEFT_RIGHT; 100 | case BOTTOM_TOP: 101 | return TOP_BOTTOM; 102 | default: 103 | return null; 104 | } 105 | } 106 | }; 107 | 108 | 109 | /** 110 | * Create a pair of {@link FlipAnimation} that can be used to flip 3D transition from {@code fromView} to {@code toView}. A typical use case is with {@link android.widget.ViewAnimator} as an out and in transition. 111 | * 112 | * NOTE: Avoid using this method. Instead, use {@link #flipTransition}. 113 | * 114 | * @param fromView the view transition away from 115 | * @param toView the view transition to 116 | * @param dir the flip direction 117 | * @param duration the transition duration in milliseconds 118 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 119 | * @return 120 | */ 121 | public static Animation[] flipAnimation(final View fromView, final View toView, FlipDirection dir, long duration, Interpolator interpolator) { 122 | Animation[] result = new Animation[2]; 123 | float centerX; 124 | float centerY; 125 | 126 | centerX = fromView.getWidth() / 2.0f; 127 | centerY = fromView.getHeight() / 2.0f; 128 | 129 | FlipAnimation outFlip= new FlipAnimation(dir.getStartDegreeForFirstView(), dir.getEndDegreeForFirstView(), centerX, centerY, FlipAnimation.SCALE_DEFAULT, FlipAnimation.ScaleUpDownEnum.SCALE_DOWN); 130 | outFlip.setDuration(duration); 131 | outFlip.setFillAfter(true); 132 | outFlip.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 133 | 134 | if (dir == FlipDirection.BOTTOM_TOP || dir == FlipDirection.TOP_BOTTOM) 135 | outFlip.setDirection(FlipAnimation.ROTATION_X); 136 | else 137 | outFlip.setDirection(FlipAnimation.ROTATION_Y); 138 | 139 | AnimationSet outAnimation = new AnimationSet(true); 140 | outAnimation.addAnimation(outFlip); 141 | result[0] = outAnimation; 142 | 143 | // Uncomment the following if toView has its layout established (not the case if using ViewFlipper and on first show) 144 | //centerX = toView.getWidth() / 2.0f; 145 | //centerY = toView.getHeight() / 2.0f; 146 | 147 | FlipAnimation inFlip = new FlipAnimation(dir.getStartDegreeForSecondView(), dir.getEndDegreeForSecondView(), centerX, centerY, FlipAnimation.SCALE_DEFAULT, FlipAnimation.ScaleUpDownEnum.SCALE_UP); 148 | inFlip.setDuration(duration); 149 | inFlip.setFillAfter(true); 150 | inFlip.setInterpolator(interpolator == null ? new AccelerateInterpolator() : interpolator); 151 | inFlip.setStartOffset(duration); 152 | 153 | if (dir == FlipDirection.BOTTOM_TOP || dir == FlipDirection.TOP_BOTTOM) 154 | inFlip.setDirection(FlipAnimation.ROTATION_X); 155 | else 156 | inFlip.setDirection(FlipAnimation.ROTATION_Y); 157 | 158 | AnimationSet inAnimation = new AnimationSet(true); 159 | inAnimation.addAnimation(inFlip); 160 | result[1] = inAnimation; 161 | 162 | return result; 163 | 164 | } 165 | 166 | /** 167 | * Flip to the next view of the {@code ViewAnimator}'s subviews. A call to this method will initiate a {@link FlipAnimation} to show the next View. 168 | * If the currently visible view is the last view, flip direction will be reversed for this transition. 169 | * 170 | * @param viewAnimator the {@code ViewAnimator} 171 | * @param dir the direction of flip 172 | */ 173 | public static void flipTransition(final ViewAnimator viewAnimator, FlipDirection dir) { 174 | flipTransition(viewAnimator, dir, DEFAULT_FLIP_TRANSITION_DURATION); 175 | } 176 | 177 | /** 178 | * Flip to the next view of the {@code ViewAnimator}'s subviews. A call to this method will initiate a {@link FlipAnimation} to show the next View. 179 | * If the currently visible view is the last view, flip direction will be reversed for this transition. 180 | * 181 | * @param viewAnimator the {@code ViewAnimator} 182 | * @param dir the direction of flip 183 | * @param duration the transition duration in milliseconds 184 | */ 185 | public static void flipTransition(final ViewAnimator viewAnimator, FlipDirection dir, long duration) { 186 | 187 | final View fromView = viewAnimator.getCurrentView(); 188 | final int currentIndex = viewAnimator.getDisplayedChild(); 189 | final int nextIndex = (currentIndex + 1)%viewAnimator.getChildCount(); 190 | 191 | final View toView = viewAnimator.getChildAt(nextIndex); 192 | 193 | Animation[] animc = AnimationFactory.flipAnimation(fromView, toView, (nextIndex < currentIndex?dir.theOtherDirection():dir), duration, null); 194 | 195 | viewAnimator.setOutAnimation(animc[0]); 196 | viewAnimator.setInAnimation(animc[1]); 197 | 198 | viewAnimator.showNext(); 199 | } 200 | 201 | ////////////// 202 | 203 | 204 | /** 205 | * Slide animations to enter a view from left. 206 | * 207 | * @param duration the animation duration in milliseconds 208 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 209 | * @return a slide transition animation 210 | */ 211 | public static Animation inFromLeftAnimation(long duration, Interpolator interpolator) { 212 | Animation inFromLeft = new TranslateAnimation( 213 | Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 214 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 215 | ); 216 | inFromLeft.setDuration(duration); 217 | inFromLeft.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); //AccelerateInterpolator 218 | return inFromLeft; 219 | } 220 | 221 | /** 222 | * Slide animations to hide a view by sliding it to the right 223 | * 224 | * @param duration the animation duration in milliseconds 225 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 226 | * @return a slide transition animation 227 | */ 228 | public static Animation outToRightAnimation(long duration, Interpolator interpolator) { 229 | Animation outtoRight = new TranslateAnimation( 230 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, +1.0f, 231 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 232 | ); 233 | outtoRight.setDuration(duration); 234 | outtoRight.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 235 | return outtoRight; 236 | } 237 | 238 | /** 239 | * Slide animations to enter a view from right. 240 | * 241 | * @param duration the animation duration in milliseconds 242 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 243 | * @return a slide transition animation 244 | */ 245 | public static Animation inFromRightAnimation(long duration, Interpolator interpolator) { 246 | 247 | Animation inFromRight = new TranslateAnimation( 248 | Animation.RELATIVE_TO_PARENT, +1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 249 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 250 | ); 251 | inFromRight.setDuration(duration); 252 | inFromRight.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 253 | return inFromRight; 254 | } 255 | 256 | /** 257 | * Slide animations to hide a view by sliding it to the left. 258 | * 259 | * @param duration the animation duration in milliseconds 260 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 261 | * @return a slide transition animation 262 | */ 263 | public static Animation outToLeftAnimation(long duration, Interpolator interpolator) { 264 | Animation outtoLeft = new TranslateAnimation( 265 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f, 266 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 267 | ); 268 | outtoLeft.setDuration(duration); 269 | outtoLeft.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 270 | return outtoLeft; 271 | } 272 | 273 | /** 274 | * Slide animations to enter a view from top. 275 | * 276 | * @param duration the animation duration in milliseconds 277 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 278 | * @return a slide transition animation 279 | */ 280 | public static Animation inFromTopAnimation(long duration, Interpolator interpolator) { 281 | Animation infromtop = new TranslateAnimation( 282 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 283 | Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f 284 | ); 285 | infromtop.setDuration(duration); 286 | infromtop.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 287 | return infromtop; 288 | } 289 | 290 | /** 291 | * Slide animations to hide a view by sliding it to the top 292 | * 293 | * @param duration the animation duration in milliseconds 294 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 295 | * @return a slide transition animation 296 | */ 297 | public static Animation outToTopAnimation(long duration, Interpolator interpolator) { 298 | Animation outtotop = new TranslateAnimation( 299 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 300 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f 301 | ); 302 | outtotop.setDuration(duration); 303 | outtotop.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 304 | return outtotop; 305 | } 306 | 307 | /** 308 | * A fade animation that will fade the subject in by changing alpha from 0 to 1. 309 | * 310 | * @param duration the animation duration in milliseconds 311 | * @param delay how long to wait before starting the animation, in milliseconds 312 | * @return a fade animation 313 | * @see #fadeInAnimation(android.view.View, long) 314 | */ 315 | public static Animation fadeInAnimation(long duration, long delay) { 316 | 317 | Animation fadeIn = new AlphaAnimation(0, 1); 318 | fadeIn.setInterpolator(new DecelerateInterpolator()); 319 | fadeIn.setDuration(duration); 320 | fadeIn.setStartOffset(delay); 321 | 322 | return fadeIn; 323 | } 324 | 325 | /** 326 | * A fade animation that will fade the subject out by changing alpha from 1 to 0. 327 | * 328 | * @param duration the animation duration in milliseconds 329 | * @param delay how long to wait before starting the animation, in milliseconds 330 | * @return a fade animation 331 | * @see #fadeOutAnimation(android.view.View, long) 332 | */ 333 | public static Animation fadeOutAnimation(long duration, long delay) { 334 | 335 | Animation fadeOut = new AlphaAnimation(1, 0); 336 | fadeOut.setInterpolator(new AccelerateInterpolator()); 337 | fadeOut.setStartOffset(delay); 338 | fadeOut.setDuration(duration); 339 | 340 | return fadeOut; 341 | } 342 | 343 | /** 344 | * A fade animation that will ensure the View starts and ends with the correct visibility 345 | * @param view the View to be faded in 346 | * @param duration the animation duration in milliseconds 347 | * @return a fade animation that will set the visibility of the view at the start and end of animation 348 | */ 349 | public static Animation fadeInAnimation(long duration, final View view) { 350 | Animation animation = fadeInAnimation(500, 0); 351 | 352 | animation.setAnimationListener(new AnimationListener() { 353 | @Override 354 | public void onAnimationEnd(Animation animation) { 355 | view.setVisibility(View.VISIBLE); 356 | } 357 | 358 | @Override 359 | public void onAnimationRepeat(Animation animation) { 360 | } 361 | 362 | @Override 363 | public void onAnimationStart(Animation animation) { 364 | view.setVisibility(View.GONE); 365 | } 366 | }); 367 | 368 | return animation; 369 | } 370 | 371 | /** 372 | * A fade animation that will ensure the View starts and ends with the correct visibility 373 | * @param view the View to be faded out 374 | * @param duration the animation duration in milliseconds 375 | * @return a fade animation that will set the visibility of the view at the start and end of animation 376 | */ 377 | public static Animation fadeOutAnimation(long duration, final View view) { 378 | 379 | Animation animation = fadeOutAnimation(500, 0); 380 | 381 | animation.setAnimationListener(new AnimationListener() { 382 | @Override 383 | public void onAnimationEnd(Animation animation) { 384 | view.setVisibility(View.GONE); 385 | } 386 | 387 | @Override 388 | public void onAnimationRepeat(Animation animation) { 389 | } 390 | 391 | @Override 392 | public void onAnimationStart(Animation animation) { 393 | view.setVisibility(View.VISIBLE); 394 | } 395 | }); 396 | 397 | return animation; 398 | 399 | } 400 | 401 | /** 402 | * Creates a pair of animation that will fade in, delay, then fade out 403 | * @param duration the animation duration in milliseconds 404 | * @param delay how long to wait after fading in the subject and before starting the fade out 405 | * @return a fade in then out animations 406 | */ 407 | public static Animation[] fadeInThenOutAnimation(long duration, long delay) { 408 | return new Animation[] {fadeInAnimation(duration,0), fadeOutAnimation(duration, duration+delay)}; 409 | } 410 | 411 | /** 412 | * Fades the view in. Animation starts right away. 413 | * @param v the view to be faded in 414 | */ 415 | public static void fadeOut(View v) { 416 | if (v==null) return; 417 | v.startAnimation(fadeOutAnimation(500, v)); 418 | } 419 | 420 | /** 421 | * Fades the view out. Animation starts right away. 422 | * @param v the view to be faded out 423 | */ 424 | public static void fadeIn(View v) { 425 | if (v==null) return; 426 | 427 | v.startAnimation(fadeInAnimation(500, v)); 428 | } 429 | 430 | /** 431 | * Fades the view in, delays the specified amount of time, then fades the view out 432 | * @param v the view to be faded in then out 433 | * @param delay how long the view will be visible for 434 | */ 435 | public static void fadeInThenOut(final View v, long delay) { 436 | if (v==null) return; 437 | 438 | v.setVisibility(View.VISIBLE); 439 | AnimationSet animation = new AnimationSet(true); 440 | Animation[] fadeInOut = fadeInThenOutAnimation(500,delay); 441 | animation.addAnimation(fadeInOut[0]); 442 | animation.addAnimation(fadeInOut[1]); 443 | animation.setAnimationListener(new AnimationListener() { 444 | @Override 445 | public void onAnimationEnd(Animation animation) { 446 | v.setVisibility(View.GONE); 447 | } 448 | @Override 449 | public void onAnimationRepeat(Animation animation) { 450 | } 451 | @Override 452 | public void onAnimationStart(Animation animation) { 453 | v.setVisibility(View.VISIBLE); 454 | } 455 | }); 456 | 457 | v.startAnimation(animation); 458 | } 459 | 460 | } 461 | --------------------------------------------------------------------------------
87 | * images become available -> emit to imagesBus 88 | *
89 | * on emit to imagesBus -> try to add new rows 90 | *
91 | * NOTE: imagesBus drops events followed by another event within 300ms 92 | */ 93 | private void initSubs() { 94 | searchTermObservable() 95 | .observeOn(Schedulers.io()) 96 | .flatMap(s -> dao.fetchImageResults(new ImageRequest(s))) 97 | .doOnNext(que::add) 98 | .doOnError(throwable -> { 99 | throw new RuntimeException(throwable); 100 | }) 101 | .subscribe(imagesBus); 102 | 103 | imagesBus.debounce(50, TimeUnit.MILLISECONDS, Schedulers.computation()) 104 | .filter(o -> isLastRowVisible() && que.size() > 3) 105 | .observeOn(AndroidSchedulers.mainThread()) 106 | .subscribe(o -> displayNextRow()); 107 | } 108 | 109 | 110 | /** 111 | * If scrolled to last row, emit new item to imagesBus 112 | */ 113 | private void addOnScrollListener() { 114 | view.cardsLayout.getViewTreeObserver().addOnScrollChangedListener(() -> { 115 | imagesBus.onNext(que.peek()); 116 | }); 117 | } 118 | 119 | /** 120 | * gets images from queue and binds to newly created views 121 | */ 122 | public void displayNextRow() { 123 | Log.e(this.getClass().getSimpleName(), "last row is visible on screen, load next rows"); 124 | view.addRow(); 125 | 126 | } 127 | 128 | public void drawNewRowIfNeeded() { 129 | imagesBus.onNext(que.peek()); 130 | } 131 | 132 | 133 | /** 134 | * clear queue and view of previous results 135 | */ 136 | public void clearResults() { 137 | view.cardsLayout.removeAllViews(); 138 | que.clear(); 139 | } 140 | 141 | 142 | /** 143 | * determines whether the last row of images is visible on the screen 144 | * 145 | * @return boolean 146 | */ 147 | public boolean isLastRowVisible() { 148 | if (que.size() == 0 || view == null) { 149 | return false; 150 | } 151 | Rect scrollBounds = new Rect(); 152 | view.cardsLayout.getHitRect(scrollBounds); 153 | View lastRow = view.cardsLayout.getChildAt(view.cardsLayout.getChildCount() - 1); 154 | return lastRow == null || lastRow.getLocalVisibleRect(scrollBounds); 155 | } 156 | 157 | /** 158 | * unsubscribe when view is destroyed 159 | */ 160 | @Override 161 | public void dropView() { 162 | view = null; 163 | subs.unsubscribeAll(); 164 | } 165 | 166 | public void changeSearchTerm(String s) { 167 | view.updateSearchView(s); 168 | } 169 | 170 | public PublishSubject getHistoryViewBus() { 171 | return historyViewBus; 172 | } 173 | 174 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/model/Result.java: -------------------------------------------------------------------------------- 1 | 2 | package com.rx.demo.model; 3 | 4 | public class Result { 5 | 6 | private String GsearchResultClass; 7 | private String width; 8 | private String height; 9 | private String imageId; 10 | private String tbWidth; 11 | private String tbHeight; 12 | private String unescapedUrl; 13 | private String url; 14 | private String visibleUrl; 15 | private String title; 16 | private String titleNoFormatting; 17 | private String originalContextUrl; 18 | private String content; 19 | private String contentNoFormatting; 20 | private String tbUrl; 21 | 22 | /** 23 | * 24 | * @return 25 | * The GsearchResultClass 26 | */ 27 | public String getGsearchResultClass() { 28 | return GsearchResultClass; 29 | } 30 | 31 | /** 32 | * 33 | * @param GsearchResultClass 34 | * The GsearchResultClass 35 | */ 36 | public void setGsearchResultClass(String GsearchResultClass) { 37 | this.GsearchResultClass = GsearchResultClass; 38 | } 39 | 40 | /** 41 | * 42 | * @return 43 | * The width 44 | */ 45 | public String getWidth() { 46 | return width; 47 | } 48 | 49 | /** 50 | * 51 | * @param width 52 | * The width 53 | */ 54 | public void setWidth(String width) { 55 | this.width = width; 56 | } 57 | 58 | /** 59 | * 60 | * @return 61 | * The height 62 | */ 63 | public String getHeight() { 64 | return height; 65 | } 66 | 67 | /** 68 | * 69 | * @param height 70 | * The height 71 | */ 72 | public void setHeight(String height) { 73 | this.height = height; 74 | } 75 | 76 | /** 77 | * 78 | * @return 79 | * The imageId 80 | */ 81 | public String getImageId() { 82 | return imageId; 83 | } 84 | 85 | /** 86 | * 87 | * @param imageId 88 | * The imageId 89 | */ 90 | public void setImageId(String imageId) { 91 | this.imageId = imageId; 92 | } 93 | 94 | /** 95 | * 96 | * @return 97 | * The tbWidth 98 | */ 99 | public String getTbWidth() { 100 | return tbWidth; 101 | } 102 | 103 | /** 104 | * 105 | * @param tbWidth 106 | * The tbWidth 107 | */ 108 | public void setTbWidth(String tbWidth) { 109 | this.tbWidth = tbWidth; 110 | } 111 | 112 | /** 113 | * 114 | * @return 115 | * The tbHeight 116 | */ 117 | public String getTbHeight() { 118 | return tbHeight; 119 | } 120 | 121 | /** 122 | * 123 | * @param tbHeight 124 | * The tbHeight 125 | */ 126 | public void setTbHeight(String tbHeight) { 127 | this.tbHeight = tbHeight; 128 | } 129 | 130 | /** 131 | * 132 | * @return 133 | * The unescapedUrl 134 | */ 135 | public String getUnescapedUrl() { 136 | return unescapedUrl; 137 | } 138 | 139 | /** 140 | * 141 | * @param unescapedUrl 142 | * The unescapedUrl 143 | */ 144 | public void setUnescapedUrl(String unescapedUrl) { 145 | this.unescapedUrl = unescapedUrl; 146 | } 147 | 148 | /** 149 | * 150 | * @return 151 | * The url 152 | */ 153 | public String getUrl() { 154 | return url; 155 | } 156 | 157 | /** 158 | * 159 | * @param url 160 | * The url 161 | */ 162 | public void setUrl(String url) { 163 | this.url = url; 164 | } 165 | 166 | /** 167 | * 168 | * @return 169 | * The visibleUrl 170 | */ 171 | public String getVisibleUrl() { 172 | return visibleUrl; 173 | } 174 | 175 | /** 176 | * 177 | * @param visibleUrl 178 | * The visibleUrl 179 | */ 180 | public void setVisibleUrl(String visibleUrl) { 181 | this.visibleUrl = visibleUrl; 182 | } 183 | 184 | /** 185 | * 186 | * @return 187 | * The title 188 | */ 189 | public String getTitle() { 190 | return title; 191 | } 192 | 193 | /** 194 | * 195 | * @param title 196 | * The title 197 | */ 198 | public void setTitle(String title) { 199 | this.title = title; 200 | } 201 | 202 | /** 203 | * 204 | * @return 205 | * The titleNoFormatting 206 | */ 207 | public String getTitleNoFormatting() { 208 | return titleNoFormatting; 209 | } 210 | 211 | /** 212 | * 213 | * @param titleNoFormatting 214 | * The titleNoFormatting 215 | */ 216 | public void setTitleNoFormatting(String titleNoFormatting) { 217 | this.titleNoFormatting = titleNoFormatting; 218 | } 219 | 220 | /** 221 | * 222 | * @return 223 | * The originalContextUrl 224 | */ 225 | public String getOriginalContextUrl() { 226 | return originalContextUrl; 227 | } 228 | 229 | /** 230 | * 231 | * @param originalContextUrl 232 | * The originalContextUrl 233 | */ 234 | public void setOriginalContextUrl(String originalContextUrl) { 235 | this.originalContextUrl = originalContextUrl; 236 | } 237 | 238 | /** 239 | * 240 | * @return 241 | * The content 242 | */ 243 | public String getContent() { 244 | return content; 245 | } 246 | 247 | /** 248 | * 249 | * @param content 250 | * The content 251 | */ 252 | public void setContent(String content) { 253 | this.content = content; 254 | } 255 | 256 | /** 257 | * 258 | * @return 259 | * The contentNoFormatting 260 | */ 261 | public String getContentNoFormatting() { 262 | return contentNoFormatting; 263 | } 264 | 265 | /** 266 | * 267 | * @param contentNoFormatting 268 | * The contentNoFormatting 269 | */ 270 | public void setContentNoFormatting(String contentNoFormatting) { 271 | this.contentNoFormatting = contentNoFormatting; 272 | } 273 | 274 | /** 275 | * 276 | * @return 277 | * The tbUrl 278 | */ 279 | public String getTbUrl() { 280 | return tbUrl; 281 | } 282 | 283 | /** 284 | * 285 | * @param tbUrl 286 | * The tbUrl 287 | */ 288 | public void setTbUrl(String tbUrl) { 289 | this.tbUrl = tbUrl; 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/animation/FlipAnimation.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012 Ephraim Tekle genzeb@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | * associated documentation files (the "Software"), to deal in the Software without restriction, including 6 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 8 | * following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all copies or substantial 11 | * portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 14 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 15 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | * 19 | * @author Ephraim A. Tekle 20 | * 21 | */ 22 | package com.rx.demo.ui.animation; 23 | 24 | import android.view.animation.Animation; 25 | import android.graphics.Camera; 26 | import android.graphics.Matrix; 27 | import android.view.animation.Transformation; 28 | 29 | /** 30 | * This class extends Animation to support a 3D flip view transition animation. Two instances of this class is 31 | * required: one for the "from" view and another for the "to" view. 32 | * 33 | * NOTE: use {@link AnimationFactory} to use this class. 34 | * 35 | * @author Ephraim A. Tekle 36 | * 37 | */ 38 | public class FlipAnimation extends Animation { 39 | public static final int ROTATION_X = 0; 40 | public static final int ROTATION_Y = 1; 41 | private final float mFromDegrees; 42 | private final float mToDegrees; 43 | private final float mCenterX; 44 | private final float mCenterY; 45 | private Camera mCamera; 46 | private int mDirection; 47 | 48 | private final ScaleUpDownEnum scaleType; 49 | 50 | /** 51 | * How much to scale up/down. The default scale of 75% of full size seems optimal based on testing. Feel free to experiment away, however. 52 | */ 53 | public static final float SCALE_DEFAULT = 0.75f; 54 | 55 | private float scale; 56 | 57 | /** 58 | * Constructs a new {@code FlipAnimation} object.Two {@code FlipAnimation} objects are needed for a complete transition b/n two views. 59 | * 60 | * @param fromDegrees the start angle in degrees for a rotation along the y-axis, i.e. in-and-out of the screen, i.e. 3D flip. This should really be multiple of 90 degrees. 61 | * @param toDegrees the end angle in degrees for a rotation along the y-axis, i.e. in-and-out of the screen, i.e. 3D flip. This should really be multiple of 90 degrees. 62 | * @param centerX the x-axis value of the center of rotation 63 | * @param centerY the y-axis value of the center of rotation 64 | * @param scale to get a 3D effect, the transition views need to be zoomed (scaled). This value must be b/n (0,1) or else the default scale {@link #SCALE_DEFAULT} is used. 65 | * @param scaleType flip view transition is broken down into two: the zoom-out of the "from" view and the zoom-in of the "to" view. This parameter is used to determine which is being done. See {@link com.rx.demo.ui.animation.FlipAnimation.ScaleUpDownEnum}. 66 | */ 67 | public FlipAnimation(float fromDegrees, float toDegrees, float centerX, float centerY, float scale, ScaleUpDownEnum scaleType) { 68 | mFromDegrees = fromDegrees; 69 | mToDegrees = toDegrees; 70 | mCenterX = centerX; 71 | mCenterY = centerY; 72 | this.scale = (scale<=0||scale>=1)?SCALE_DEFAULT:scale; 73 | this.scaleType = scaleType==null?ScaleUpDownEnum.SCALE_CYCLE:scaleType; 74 | mDirection = ROTATION_Y; 75 | } 76 | 77 | @Override 78 | public void initialize(int width, int height, int parentWidth, int parentHeight) { 79 | super.initialize(width, height, parentWidth, parentHeight); 80 | mCamera = new Camera(); 81 | } 82 | 83 | @Override 84 | protected void applyTransformation(float interpolatedTime, Transformation t) { 85 | final float fromDegrees = mFromDegrees; 86 | float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); 87 | 88 | final float centerX = mCenterX; 89 | final float centerY = mCenterY; 90 | final Camera camera = mCamera; 91 | 92 | final Matrix matrix = t.getMatrix(); 93 | 94 | camera.save(); 95 | 96 | if (mDirection == ROTATION_X) 97 | camera.rotateX(degrees); 98 | else 99 | camera.rotateY(degrees); 100 | 101 | camera.getMatrix(matrix); 102 | camera.restore(); 103 | 104 | matrix.preTranslate(-centerX, -centerY); 105 | matrix.postTranslate(centerX, centerY); 106 | 107 | matrix.preScale(scaleType.getScale(scale, interpolatedTime), scaleType.getScale(scale, interpolatedTime), centerX, centerY); 108 | 109 | } 110 | 111 | /** 112 | * Get the current direction, it can be {@link #ROTATION_X} or {#ROTATION_Y} 113 | * @param direction 114 | */ 115 | public void setDirection(int direction) { 116 | mDirection = direction; 117 | } 118 | 119 | /** 120 | * This enumeration is used to determine the zoom (or scale) behavior of a {@link com.rx.demo.ui.animation.FlipAnimation}. 121 | * 122 | * @author Ephraim A. Tekle 123 | * 124 | */ 125 | public static enum ScaleUpDownEnum { 126 | /** 127 | * The view will be scaled up from the scale value until it's at 100% zoom level (i.e. no zoom). 128 | */ 129 | SCALE_UP, 130 | /** 131 | * The view will be scaled down starting at no zoom (100% zoom level) until it's at a specified zoom level. 132 | */ 133 | SCALE_DOWN, 134 | /** 135 | * The view will cycle through a zoom down and then zoom up. 136 | */ 137 | SCALE_CYCLE, 138 | /** 139 | * No zoom effect is applied. 140 | */ 141 | SCALE_NONE; 142 | 143 | /** 144 | * The intermittent zoom level given the current or desired maximum zoom level for the specified iteration 145 | * 146 | * @param max the maximum desired or current zoom level 147 | * @param iter the iteration (from 0..1). 148 | * @return the current zoom level 149 | */ 150 | public float getScale(float max, float iter) { 151 | switch(this) { 152 | case SCALE_UP: 153 | return max + (1-max)*iter; 154 | 155 | case SCALE_DOWN: 156 | return 1 - (1-max)*iter; 157 | 158 | case SCALE_CYCLE: { 159 | final boolean halfWay = (iter > 0.5); 160 | 161 | if (halfWay) { 162 | return max + (1-max)*(iter-0.5f)*2; 163 | } else { 164 | return 1 - (1-max)*(iter*2); 165 | } 166 | } 167 | 168 | default: 169 | return 1; 170 | } 171 | } 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/animation/AnimationFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012 Ephraim Tekle genzeb@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | * associated documentation files (the "Software"), to deal in the Software without restriction, including 6 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 8 | * following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all copies or substantial 11 | * portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 14 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 15 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | * 19 | * @author Ephraim A. Tekle 20 | * 21 | */ 22 | package com.rx.demo.ui.animation; 23 | 24 | import android.view.View; 25 | import android.view.animation.AccelerateInterpolator; 26 | import android.view.animation.AlphaAnimation; 27 | import android.view.animation.Animation; 28 | import android.view.animation.AnimationSet; 29 | import android.view.animation.DecelerateInterpolator; 30 | import android.view.animation.Interpolator; 31 | import android.view.animation.TranslateAnimation; 32 | import android.view.animation.Animation.AnimationListener; 33 | import android.widget.ViewAnimator; 34 | 35 | /** 36 | * This class contains methods for creating {@link android.view.animation.Animation} objects for some of the most common animation, including a 3D flip animation, {@link FlipAnimation}. 37 | * Furthermore, utility methods are provided for initiating fade-in-then-out and flip animations. 38 | * 39 | * @author Ephraim A. Tekle 40 | * 41 | */ 42 | public class AnimationFactory { 43 | 44 | private static final int DEFAULT_FLIP_TRANSITION_DURATION = 500; 45 | 46 | /** 47 | * The {@code FlipDirection} enumeration defines the most typical flip view transitions: left-to-right and right-to-left. {@code FlipDirection} is used during the creation of {@link FlipAnimation} animations. 48 | * 49 | * @author Ephraim A. Tekle 50 | * 51 | */ 52 | public static enum FlipDirection { 53 | LEFT_RIGHT, 54 | RIGHT_LEFT, 55 | TOP_BOTTOM, 56 | BOTTOM_TOP; 57 | 58 | public float getStartDegreeForFirstView() { 59 | return 0; 60 | } 61 | 62 | public float getStartDegreeForSecondView() { 63 | switch(this) { 64 | case LEFT_RIGHT: 65 | case TOP_BOTTOM: 66 | return -90; 67 | case RIGHT_LEFT: 68 | case BOTTOM_TOP: 69 | return 90; 70 | default: 71 | return 0; 72 | } 73 | } 74 | 75 | public float getEndDegreeForFirstView() { 76 | switch(this) { 77 | case LEFT_RIGHT: 78 | case TOP_BOTTOM: 79 | return 90; 80 | case RIGHT_LEFT: 81 | case BOTTOM_TOP: 82 | return -90; 83 | default: 84 | return 0; 85 | } 86 | } 87 | 88 | public float getEndDegreeForSecondView() { 89 | return 0; 90 | } 91 | 92 | public FlipDirection theOtherDirection() { 93 | switch(this) { 94 | case LEFT_RIGHT: 95 | return RIGHT_LEFT; 96 | case TOP_BOTTOM: 97 | return BOTTOM_TOP; 98 | case RIGHT_LEFT: 99 | return LEFT_RIGHT; 100 | case BOTTOM_TOP: 101 | return TOP_BOTTOM; 102 | default: 103 | return null; 104 | } 105 | } 106 | }; 107 | 108 | 109 | /** 110 | * Create a pair of {@link FlipAnimation} that can be used to flip 3D transition from {@code fromView} to {@code toView}. A typical use case is with {@link android.widget.ViewAnimator} as an out and in transition. 111 | * 112 | * NOTE: Avoid using this method. Instead, use {@link #flipTransition}. 113 | * 114 | * @param fromView the view transition away from 115 | * @param toView the view transition to 116 | * @param dir the flip direction 117 | * @param duration the transition duration in milliseconds 118 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 119 | * @return 120 | */ 121 | public static Animation[] flipAnimation(final View fromView, final View toView, FlipDirection dir, long duration, Interpolator interpolator) { 122 | Animation[] result = new Animation[2]; 123 | float centerX; 124 | float centerY; 125 | 126 | centerX = fromView.getWidth() / 2.0f; 127 | centerY = fromView.getHeight() / 2.0f; 128 | 129 | FlipAnimation outFlip= new FlipAnimation(dir.getStartDegreeForFirstView(), dir.getEndDegreeForFirstView(), centerX, centerY, FlipAnimation.SCALE_DEFAULT, FlipAnimation.ScaleUpDownEnum.SCALE_DOWN); 130 | outFlip.setDuration(duration); 131 | outFlip.setFillAfter(true); 132 | outFlip.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 133 | 134 | if (dir == FlipDirection.BOTTOM_TOP || dir == FlipDirection.TOP_BOTTOM) 135 | outFlip.setDirection(FlipAnimation.ROTATION_X); 136 | else 137 | outFlip.setDirection(FlipAnimation.ROTATION_Y); 138 | 139 | AnimationSet outAnimation = new AnimationSet(true); 140 | outAnimation.addAnimation(outFlip); 141 | result[0] = outAnimation; 142 | 143 | // Uncomment the following if toView has its layout established (not the case if using ViewFlipper and on first show) 144 | //centerX = toView.getWidth() / 2.0f; 145 | //centerY = toView.getHeight() / 2.0f; 146 | 147 | FlipAnimation inFlip = new FlipAnimation(dir.getStartDegreeForSecondView(), dir.getEndDegreeForSecondView(), centerX, centerY, FlipAnimation.SCALE_DEFAULT, FlipAnimation.ScaleUpDownEnum.SCALE_UP); 148 | inFlip.setDuration(duration); 149 | inFlip.setFillAfter(true); 150 | inFlip.setInterpolator(interpolator == null ? new AccelerateInterpolator() : interpolator); 151 | inFlip.setStartOffset(duration); 152 | 153 | if (dir == FlipDirection.BOTTOM_TOP || dir == FlipDirection.TOP_BOTTOM) 154 | inFlip.setDirection(FlipAnimation.ROTATION_X); 155 | else 156 | inFlip.setDirection(FlipAnimation.ROTATION_Y); 157 | 158 | AnimationSet inAnimation = new AnimationSet(true); 159 | inAnimation.addAnimation(inFlip); 160 | result[1] = inAnimation; 161 | 162 | return result; 163 | 164 | } 165 | 166 | /** 167 | * Flip to the next view of the {@code ViewAnimator}'s subviews. A call to this method will initiate a {@link FlipAnimation} to show the next View. 168 | * If the currently visible view is the last view, flip direction will be reversed for this transition. 169 | * 170 | * @param viewAnimator the {@code ViewAnimator} 171 | * @param dir the direction of flip 172 | */ 173 | public static void flipTransition(final ViewAnimator viewAnimator, FlipDirection dir) { 174 | flipTransition(viewAnimator, dir, DEFAULT_FLIP_TRANSITION_DURATION); 175 | } 176 | 177 | /** 178 | * Flip to the next view of the {@code ViewAnimator}'s subviews. A call to this method will initiate a {@link FlipAnimation} to show the next View. 179 | * If the currently visible view is the last view, flip direction will be reversed for this transition. 180 | * 181 | * @param viewAnimator the {@code ViewAnimator} 182 | * @param dir the direction of flip 183 | * @param duration the transition duration in milliseconds 184 | */ 185 | public static void flipTransition(final ViewAnimator viewAnimator, FlipDirection dir, long duration) { 186 | 187 | final View fromView = viewAnimator.getCurrentView(); 188 | final int currentIndex = viewAnimator.getDisplayedChild(); 189 | final int nextIndex = (currentIndex + 1)%viewAnimator.getChildCount(); 190 | 191 | final View toView = viewAnimator.getChildAt(nextIndex); 192 | 193 | Animation[] animc = AnimationFactory.flipAnimation(fromView, toView, (nextIndex < currentIndex?dir.theOtherDirection():dir), duration, null); 194 | 195 | viewAnimator.setOutAnimation(animc[0]); 196 | viewAnimator.setInAnimation(animc[1]); 197 | 198 | viewAnimator.showNext(); 199 | } 200 | 201 | ////////////// 202 | 203 | 204 | /** 205 | * Slide animations to enter a view from left. 206 | * 207 | * @param duration the animation duration in milliseconds 208 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 209 | * @return a slide transition animation 210 | */ 211 | public static Animation inFromLeftAnimation(long duration, Interpolator interpolator) { 212 | Animation inFromLeft = new TranslateAnimation( 213 | Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 214 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 215 | ); 216 | inFromLeft.setDuration(duration); 217 | inFromLeft.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); //AccelerateInterpolator 218 | return inFromLeft; 219 | } 220 | 221 | /** 222 | * Slide animations to hide a view by sliding it to the right 223 | * 224 | * @param duration the animation duration in milliseconds 225 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 226 | * @return a slide transition animation 227 | */ 228 | public static Animation outToRightAnimation(long duration, Interpolator interpolator) { 229 | Animation outtoRight = new TranslateAnimation( 230 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, +1.0f, 231 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 232 | ); 233 | outtoRight.setDuration(duration); 234 | outtoRight.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 235 | return outtoRight; 236 | } 237 | 238 | /** 239 | * Slide animations to enter a view from right. 240 | * 241 | * @param duration the animation duration in milliseconds 242 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 243 | * @return a slide transition animation 244 | */ 245 | public static Animation inFromRightAnimation(long duration, Interpolator interpolator) { 246 | 247 | Animation inFromRight = new TranslateAnimation( 248 | Animation.RELATIVE_TO_PARENT, +1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 249 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 250 | ); 251 | inFromRight.setDuration(duration); 252 | inFromRight.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 253 | return inFromRight; 254 | } 255 | 256 | /** 257 | * Slide animations to hide a view by sliding it to the left. 258 | * 259 | * @param duration the animation duration in milliseconds 260 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 261 | * @return a slide transition animation 262 | */ 263 | public static Animation outToLeftAnimation(long duration, Interpolator interpolator) { 264 | Animation outtoLeft = new TranslateAnimation( 265 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f, 266 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 267 | ); 268 | outtoLeft.setDuration(duration); 269 | outtoLeft.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 270 | return outtoLeft; 271 | } 272 | 273 | /** 274 | * Slide animations to enter a view from top. 275 | * 276 | * @param duration the animation duration in milliseconds 277 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 278 | * @return a slide transition animation 279 | */ 280 | public static Animation inFromTopAnimation(long duration, Interpolator interpolator) { 281 | Animation infromtop = new TranslateAnimation( 282 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 283 | Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f 284 | ); 285 | infromtop.setDuration(duration); 286 | infromtop.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 287 | return infromtop; 288 | } 289 | 290 | /** 291 | * Slide animations to hide a view by sliding it to the top 292 | * 293 | * @param duration the animation duration in milliseconds 294 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 295 | * @return a slide transition animation 296 | */ 297 | public static Animation outToTopAnimation(long duration, Interpolator interpolator) { 298 | Animation outtotop = new TranslateAnimation( 299 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 300 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f 301 | ); 302 | outtotop.setDuration(duration); 303 | outtotop.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 304 | return outtotop; 305 | } 306 | 307 | /** 308 | * A fade animation that will fade the subject in by changing alpha from 0 to 1. 309 | * 310 | * @param duration the animation duration in milliseconds 311 | * @param delay how long to wait before starting the animation, in milliseconds 312 | * @return a fade animation 313 | * @see #fadeInAnimation(android.view.View, long) 314 | */ 315 | public static Animation fadeInAnimation(long duration, long delay) { 316 | 317 | Animation fadeIn = new AlphaAnimation(0, 1); 318 | fadeIn.setInterpolator(new DecelerateInterpolator()); 319 | fadeIn.setDuration(duration); 320 | fadeIn.setStartOffset(delay); 321 | 322 | return fadeIn; 323 | } 324 | 325 | /** 326 | * A fade animation that will fade the subject out by changing alpha from 1 to 0. 327 | * 328 | * @param duration the animation duration in milliseconds 329 | * @param delay how long to wait before starting the animation, in milliseconds 330 | * @return a fade animation 331 | * @see #fadeOutAnimation(android.view.View, long) 332 | */ 333 | public static Animation fadeOutAnimation(long duration, long delay) { 334 | 335 | Animation fadeOut = new AlphaAnimation(1, 0); 336 | fadeOut.setInterpolator(new AccelerateInterpolator()); 337 | fadeOut.setStartOffset(delay); 338 | fadeOut.setDuration(duration); 339 | 340 | return fadeOut; 341 | } 342 | 343 | /** 344 | * A fade animation that will ensure the View starts and ends with the correct visibility 345 | * @param view the View to be faded in 346 | * @param duration the animation duration in milliseconds 347 | * @return a fade animation that will set the visibility of the view at the start and end of animation 348 | */ 349 | public static Animation fadeInAnimation(long duration, final View view) { 350 | Animation animation = fadeInAnimation(500, 0); 351 | 352 | animation.setAnimationListener(new AnimationListener() { 353 | @Override 354 | public void onAnimationEnd(Animation animation) { 355 | view.setVisibility(View.VISIBLE); 356 | } 357 | 358 | @Override 359 | public void onAnimationRepeat(Animation animation) { 360 | } 361 | 362 | @Override 363 | public void onAnimationStart(Animation animation) { 364 | view.setVisibility(View.GONE); 365 | } 366 | }); 367 | 368 | return animation; 369 | } 370 | 371 | /** 372 | * A fade animation that will ensure the View starts and ends with the correct visibility 373 | * @param view the View to be faded out 374 | * @param duration the animation duration in milliseconds 375 | * @return a fade animation that will set the visibility of the view at the start and end of animation 376 | */ 377 | public static Animation fadeOutAnimation(long duration, final View view) { 378 | 379 | Animation animation = fadeOutAnimation(500, 0); 380 | 381 | animation.setAnimationListener(new AnimationListener() { 382 | @Override 383 | public void onAnimationEnd(Animation animation) { 384 | view.setVisibility(View.GONE); 385 | } 386 | 387 | @Override 388 | public void onAnimationRepeat(Animation animation) { 389 | } 390 | 391 | @Override 392 | public void onAnimationStart(Animation animation) { 393 | view.setVisibility(View.VISIBLE); 394 | } 395 | }); 396 | 397 | return animation; 398 | 399 | } 400 | 401 | /** 402 | * Creates a pair of animation that will fade in, delay, then fade out 403 | * @param duration the animation duration in milliseconds 404 | * @param delay how long to wait after fading in the subject and before starting the fade out 405 | * @return a fade in then out animations 406 | */ 407 | public static Animation[] fadeInThenOutAnimation(long duration, long delay) { 408 | return new Animation[] {fadeInAnimation(duration,0), fadeOutAnimation(duration, duration+delay)}; 409 | } 410 | 411 | /** 412 | * Fades the view in. Animation starts right away. 413 | * @param v the view to be faded in 414 | */ 415 | public static void fadeOut(View v) { 416 | if (v==null) return; 417 | v.startAnimation(fadeOutAnimation(500, v)); 418 | } 419 | 420 | /** 421 | * Fades the view out. Animation starts right away. 422 | * @param v the view to be faded out 423 | */ 424 | public static void fadeIn(View v) { 425 | if (v==null) return; 426 | 427 | v.startAnimation(fadeInAnimation(500, v)); 428 | } 429 | 430 | /** 431 | * Fades the view in, delays the specified amount of time, then fades the view out 432 | * @param v the view to be faded in then out 433 | * @param delay how long the view will be visible for 434 | */ 435 | public static void fadeInThenOut(final View v, long delay) { 436 | if (v==null) return; 437 | 438 | v.setVisibility(View.VISIBLE); 439 | AnimationSet animation = new AnimationSet(true); 440 | Animation[] fadeInOut = fadeInThenOutAnimation(500,delay); 441 | animation.addAnimation(fadeInOut[0]); 442 | animation.addAnimation(fadeInOut[1]); 443 | animation.setAnimationListener(new AnimationListener() { 444 | @Override 445 | public void onAnimationEnd(Animation animation) { 446 | v.setVisibility(View.GONE); 447 | } 448 | @Override 449 | public void onAnimationRepeat(Animation animation) { 450 | } 451 | @Override 452 | public void onAnimationStart(Animation animation) { 453 | v.setVisibility(View.VISIBLE); 454 | } 455 | }); 456 | 457 | v.startAnimation(animation); 458 | } 459 | 460 | } 461 | --------------------------------------------------------------------------------