diff --git a/.classpath b/.classpath deleted file mode 100644 index 0df2505..0000000 --- a/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.gitignore b/.gitignore index 5c2fdd5..ed8203c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,9 @@ -gen/ -gen/* -bin/ -bin/* -target/ -target/* -.settings -tmp/ +.gradle +/local.properties +/.idea/workspace.xml .DS_Store -*.orig .idea -.jhw-cache -hardcopy.* -local.properties - +*.iml +*.ipr +*.iws +build diff --git a/.project b/.project deleted file mode 100644 index b8fa105..0000000 --- a/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - MultiColumnListView_HUEWU - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..dd9479b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: + - android + +jdk: + - oraclejdk7 + +env: + matrix: + - ANDROID_TARGET=android-19 ANDROID_ABI=armeabi-v7a + +android: + components: + - build-tools-21.1.2 + - android-21 + - sysimg-21 + +before_script: + # Create and start emulator + - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI + - emulator -avd test -no-skin -no-audio -no-window & + - adb wait-for-device + - adb shell input keyevent 82 & diff --git a/AUTHORS.md b/AUTHORS.md index df82045..9429608 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -5,8 +5,11 @@ Keep the list sorted please. * Chansuk Yang - huewu.yang at gmail.com * Dane Lipscombe - dane at 77hz.jp * DongSoo Moon - donxu at naver.com + * Feng Tao - stategrace2013 at gmail.com + * Jason Cao - mzule4j at gmail.com * Juyeong - juyeong9 at gmail.com * Leonardo TaeHwan Kim - liger745547 at gmail.com * Leonardo YongUk Kim - dalinaum at gmail.com * Thomas Urbanitsch - urbtom at bluesource.at + * Wonsik Sung - kai4th at gmail.com diff --git a/README.md b/README.md index 28dc336..da5fcc8 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,19 @@ -PLA(PinterestLikeAdapterView) +[Deprecated] PLA(PinterestLikeAdapterView) ================================== -Open source project in order to implement pinterest like list view on android. -(You can check how pinterest app looks like form below link..) +**Deprecated**: This project is not maintaned anymore. Thanks. -https://play.google.com/store/apps/details?id=com.pinterest&hl=en +[![Build Status](https://travis-ci.org/GDG-Korea/PinterestLikeAdapterView.png?branch=master)](https://travis-ci.org/GDG-Korea/PinterestLikeAdapterView)[![Stories in Ready](https://badge.waffle.io/GDG-Korea/PinterestLikeAdapterView.png?label=ready&title=Ready)](https://waffle.io/GDG-Korea/PinterestLikeAdapterView) -This project is statred based on sony deveoper's blog post 'making your own 3d list'. +Open source project in order to implement pinterest like list view on android. (You can check how [pinterest app](https://play.google.com/store/apps/details?id=com.pinterest) looks like form) -http://developer.sonymobile.com/2010/05/20/android-tutorial-making-your-own-3d-list-part-1/ +This project is statred based on Anders Ericson's blog post '[making your own 3d list](http://developer.sonymobile.com/2010/05/20/android-tutorial-making-your-own-3d-list-part-1/)'. But, currenty it is implemented based on android framework 2.3's list view source. You can check modified list view sources in internal package. Not supported Features ---------------- - +---------------------- * Entry from XML layout. * Choice Mode & Item Selection. * Filter @@ -30,31 +28,18 @@ This is a screen shot of sample activity. How to use ------------- -*Import this library with Eclipse* - -There's a issue with Eclipse when using `Existing Android Code Into Workspace.` (It' s a known bug with Eclipse.) Use `Existing Projects Into Workspace` instead. It works perfectly. - *To run Sample App.* 1. clone project. 2. run on your android phone. - 3. in option menu, you can add items or lunch pull-to-refresh sample. - - *To use Pinterest Like Multi Column View.* 1. check this project as library project. 2. MultiColumListView is the view what you need. -*To use pull-to-refresh features.* - - 1. check this project as library project. - - 2. MultiColumnPullToRefreshListView class in extra folder is what you need. - Attributes ----------- * **plaColumnNumber** @@ -131,4 +116,4 @@ License See the License for the specific language governing permissions and limitations under the License. - [3]: http://cloud.github.com/downloads/huewu/PinterestLikeAdapterView/screenshot.png + [3]: https://raw.github.com/GDG-Korea/PinterestLikeAdapterView/master/screenshot.png diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3e2f72d --- /dev/null +++ b/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.0.0' + } +} + +allprojects { + version = "1.0.3" + group = "com.huewu.pla" + + repositories { + jcenter() + } + + tasks.withType(JavaCompile) { + options.encoding = "UTF-8" + options.compilerArgs << "-Xlint:unchecked" + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5838598 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2581574 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Feb 07 22:26:15 KST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/library/.gitignore b/library/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/library/.gitignore @@ -0,0 +1 @@ +/build diff --git a/library/build.gradle b/library/build.gradle new file mode 100644 index 0000000..19a8ba6 --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,15 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 21 + buildToolsVersion '21.1.2' + + defaultConfig { + minSdkVersion 8 + targetSdkVersion 21 + } + + lintOptions { + abortOnError false + } +} \ No newline at end of file diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml new file mode 100644 index 0000000..eebd885 --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/src/main/java/com/huewu/pla/lib/DebugUtil.java b/library/src/main/java/com/huewu/pla/lib/DebugUtil.java similarity index 100% rename from src/main/java/com/huewu/pla/lib/DebugUtil.java rename to library/src/main/java/com/huewu/pla/lib/DebugUtil.java diff --git a/src/main/java/com/huewu/pla/lib/MultiColumnListView.java b/library/src/main/java/com/huewu/pla/lib/MultiColumnListView.java similarity index 78% rename from src/main/java/com/huewu/pla/lib/MultiColumnListView.java rename to library/src/main/java/com/huewu/pla/lib/MultiColumnListView.java index 8b7e664..33bf774 100644 --- a/src/main/java/com/huewu/pla/lib/MultiColumnListView.java +++ b/library/src/main/java/com/huewu/pla/lib/MultiColumnListView.java @@ -16,16 +16,18 @@ package com.huewu.pla.lib; +import com.huewu.pla.R; +import com.huewu.pla.lib.internal.PLA_AbsListView; +import com.huewu.pla.lib.internal.PLA_ListView; + import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; +import android.os.Bundle; +import android.os.Parcelable; import android.util.AttributeSet; -import android.util.SparseIntArray; import android.view.View; -import com.huewu.pla.R; -import com.huewu.pla.lib.internal.PLA_ListView; - /** * @author huewu.ynag * @date 2012-11-06 @@ -40,8 +42,8 @@ public class MultiColumnListView extends PLA_ListView { private int mColumnNumber = 2; private Column[] mColumns = null; private Column mFixedColumn = null; //column for footers & headers. - private SparseIntArray mItems = new SparseIntArray(); - + private ParcelableSparseIntArray mItems = new ParcelableSparseIntArray(); + private int mColumnPaddingLeft = 0; private int mColumnPaddingRight = 0; @@ -80,7 +82,7 @@ private void init(AttributeSet attrs) { }else{ mColumnNumber = DEFAULT_COLUMN_NUMBER; } - + mColumnPaddingLeft = a.getDimensionPixelSize(R.styleable.MultiColumnListView_plaColumnPaddingLeft, 0); mColumnPaddingRight = a.getDimensionPixelSize(R.styleable.MultiColumnListView_plaColumnPaddingRight, 0); a.recycle(); @@ -93,6 +95,14 @@ private void init(AttributeSet attrs) { mFixedColumn = new FixedColumn(); } + public void setColumnPaddingLeft(int columnPaddingLeft) { + this.mColumnPaddingLeft = columnPaddingLeft; + } + + public void setColumnPaddingRight(int columnPaddingRight) { + this.mColumnPaddingRight = columnPaddingRight; + } + /////////////////////////////////////////////////////////////////////// //Override Methods... /////////////////////////////////////////////////////////////////////// @@ -122,7 +132,7 @@ protected void onMeasureChild(View child, int position, int widthMeasureSpec, in if(isFixedView(child)) child.measure(widthMeasureSpec, heightMeasureSpec); else - child.measure(MeasureSpec.EXACTLY | getColumnWidth(position), heightMeasureSpec); + child.measure(View.MeasureSpec.EXACTLY | getColumnWidth(position), heightMeasureSpec); } @Override @@ -133,7 +143,7 @@ protected int modifyFlingInitialVelocity(int initialVelocity) { @Override protected void onItemAddedToList(int position, boolean flow ) { super.onItemAddedToList(position, flow); - + if( isHeaderOrFooterPosition(position) == false){ Column col = getNextColumn( flow, position ); mItems.append(position, col.getIndex()); @@ -156,9 +166,9 @@ protected void onLayoutSyncFinished(int syncPos) { @Override protected void onAdjustChildViews(boolean down) { - + int firstItem = getFirstVisiblePosition(); - if( down == false && firstItem == 0){ + if(!down && firstItem == 0){ final int firstColumnTop = mColumns[0].getTop(); for( Column c : mColumns ){ final int top = c.getTop(); @@ -184,11 +194,11 @@ protected int getFillChildBottom() { @Override protected int getFillChildTop() { //find largest column. - int result = Integer.MIN_VALUE; - for(Column c : mColumns){ - int top = c.getTop(); - result = result < top ? top : result; - } + int result = Integer.MIN_VALUE; + for(Column c : mColumns){ + int top = c.getTop(); + result = Math.max(result, top); + } return result; } @@ -217,10 +227,10 @@ protected int getScrollChildTop() { @Override protected int getItemLeft(int pos) { - + if( isHeaderOrFooterPosition(pos) ) return mFixedColumn.getColumnLeft(); - + return getColumnLeft(pos); } @@ -262,7 +272,7 @@ private Column getNextColumn(boolean flow, int position) { if( colIndex != -1 ){ return mColumns[colIndex]; } - + //adjust position (exclude headers...) position = Math.max(0, position - getHeaderViewsCount()); @@ -299,7 +309,7 @@ private Column gettBottomColumn() { } return result; - } + } private int getColumnLeft(int pos) { int colIndex = mItems.get(pos, -1); @@ -369,7 +379,7 @@ public int getBottom() { public void offsetTopAndBottom(int offset) { if( offset == 0 ) - return; + return; //find biggest value. int childCount = getChildCount(); @@ -390,10 +400,10 @@ public int getTop() { int childCount = getChildCount(); for( int index = 0; index < childCount; ++index ){ View v = getChildAt(index); - if(v.getLeft() != mColumnLeft && isFixedView(v) == false ) - continue; - top = top > v.getTop() ? v.getTop() : top; - } + if(v.getLeft() != mColumnLeft && !isFixedView(v)) + continue; + top = Math.min(top, v.getTop()); + } if( top == Integer.MAX_VALUE ) return mSynchedTop; //no child for this column. just return saved sync top.. @@ -402,7 +412,7 @@ public int getTop() { public void save() { mSynchedTop = 0; - mSynchedBottom = getTop(); //getBottom(); + mSynchedBottom = getTop(); } public void clear() { @@ -427,6 +437,70 @@ public int getTop() { return getScrollChildTop(); } - }//end of class - + } + + + private boolean loadingMoreComplete = true; + + public void onLoadMoreComplete() { + loadingMoreComplete = true; + } + + public interface OnLoadMoreListener { + /** + * Method to be called when scroll to buttom is requested + */ + void onLoadMore(); + } + + public OnScrollListener scroller = new OnScrollListener() { + private int visibleLastIndex = 0; + private static final int OFFSET = 2; + + @Override + public void onScrollStateChanged(PLA_AbsListView view, int scrollState) { + int lastIndex = getAdapter().getCount() - OFFSET; + if (scrollState == OnScrollListener.SCROLL_STATE_IDLE + && visibleLastIndex == lastIndex && loadingMoreComplete) { + + loadMoreListener.onLoadMore(); + loadingMoreComplete = false; + + } + } + + @Override + public void onScroll(PLA_AbsListView view, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + visibleLastIndex = firstVisibleItem + visibleItemCount - OFFSET; + } + }; + OnLoadMoreListener loadMoreListener; + + public void setOnLoadMoreListener(OnLoadMoreListener listener) { + + if (listener != null) { + this.loadMoreListener = listener; + this.setOnScrollListener(scroller); + } + }//end of class + + + @Override + public Parcelable onSaveInstanceState() { + Bundle bundle = new Bundle(); + bundle.putParcelable("instanceState", super.onSaveInstanceState()); + bundle.putParcelable("items", mItems); + return bundle; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + Bundle bundle = (Bundle) state; + mItems = bundle.getParcelable("items"); + state = bundle.getParcelable("instanceState"); + } + super.onRestoreInstanceState(state); + } }//end of class diff --git a/library/src/main/java/com/huewu/pla/lib/ParcelableSparseIntArray.java b/library/src/main/java/com/huewu/pla/lib/ParcelableSparseIntArray.java new file mode 100644 index 0000000..fe082f2 --- /dev/null +++ b/library/src/main/java/com/huewu/pla/lib/ParcelableSparseIntArray.java @@ -0,0 +1,56 @@ +package com.huewu.pla.lib; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; +import android.util.SparseIntArray; + +/** + * Created by juyeong on 7/15/14. + */ +public class ParcelableSparseIntArray extends SparseIntArray implements Parcelable { + + public ParcelableSparseIntArray() { + } + + public ParcelableSparseIntArray(int initialCapacity) { + super(initialCapacity); + } + + @SuppressWarnings("unchecked") + private ParcelableSparseIntArray(Parcel in) { + append(in.readSparseArray (ClassLoader.getSystemClassLoader())); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeSparseArray(toSparseArray()); + } + + private SparseArray toSparseArray() { + SparseArray sparseArray = new SparseArray(); + for (int i = 0, size = size(); i < size; i++) + sparseArray.append(keyAt(i), valueAt(i)); + return sparseArray; + } + + private void append(SparseArray sparseArray) { + for (int i = 0, size = sparseArray.size(); i < size; i++) + put(sparseArray.keyAt(i), sparseArray.valueAt(i)); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ParcelableSparseIntArray createFromParcel(Parcel source) { + return new ParcelableSparseIntArray(source); + } + + public ParcelableSparseIntArray[] newArray(int size) { + return new ParcelableSparseIntArray[size]; + } + }; +} diff --git a/src/main/java/com/huewu/pla/lib/internal/PLA_AbsListView.java b/library/src/main/java/com/huewu/pla/lib/internal/PLA_AbsListView.java similarity index 93% rename from src/main/java/com/huewu/pla/lib/internal/PLA_AbsListView.java rename to library/src/main/java/com/huewu/pla/lib/internal/PLA_AbsListView.java index f5cbf3a..e610e44 100644 --- a/src/main/java/com/huewu/pla/lib/internal/PLA_AbsListView.java +++ b/library/src/main/java/com/huewu/pla/lib/internal/PLA_AbsListView.java @@ -22,7 +22,9 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; +import android.os.Bundle; import android.os.Debug; +import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.view.ContextMenu.ContextMenuInfo; @@ -34,13 +36,16 @@ import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.widget.Adapter; +import android.widget.Filter; +import android.widget.Filterable; import android.widget.ListAdapter; import android.widget.Scroller; import com.huewu.pla.R; import com.huewu.pla.lib.DebugUtil; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Stack; @@ -63,6 +68,8 @@ public abstract class PLA_AbsListView extends PLA_AdapterView implements ViewTreeObserver.OnGlobalLayoutListener, ViewTreeObserver.OnTouchModeChangeListener { + private static final String TAG = "PLA_AbsListView"; + //FIXME not supported features... (removed from original AbsListView)... //Filter //Fast Scroll @@ -305,6 +312,8 @@ public abstract class PLA_AbsListView extends PLA_AdapterView imple */ boolean mScrollingCacheEnabled; + private SavedState mPendingSync; + /** * Optional callback to notify client when scroll position has changed */ @@ -351,7 +360,7 @@ public abstract class PLA_AbsListView extends PLA_AdapterView imple /** * Acts upon click */ - private PLA_AbsListView.PerformClick mPerformClick; + private PerformClick mPerformClick; /** * This view is in transcript mode -- it shows the bottom of the list when the data @@ -371,7 +380,7 @@ public abstract class PLA_AbsListView extends PLA_AdapterView imple private boolean mIsChildViewEnabled; /** - * The last scroll state reported to clients through {@link OnScrollListener}. + * The last scroll state reported to clients through {@link com.huewu.pla.lib.internal.PLA_AbsListView.OnScrollListener}. */ private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE; @@ -421,7 +430,7 @@ public interface OnScrollListener { * Callback method to be invoked while the list view or grid view is being scrolled. If the * view is being scrolled, this method will be called before the next frame of the scroll is * rendered. In particular, it will be called before any calls to - * {@link Adapter#getView(int, View, ViewGroup)}. + * {@link android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)}. * * @param view The view whose scroll state is being reported * @@ -440,7 +449,7 @@ public interface OnScrollListener { * @param totalItemCount the number of items in the list adaptor */ public void onScroll(PLA_AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount); + int totalItemCount); } public PLA_AbsListView(Context context) { @@ -449,7 +458,18 @@ public PLA_AbsListView(Context context) { setVerticalScrollBarEnabled(true); TypedArray a = context.obtainStyledAttributes(R.styleable.View); - initializeScrollbars(a); + + // FIXME: ad hoc patch + try { + // initializeScrollbars(TypedArray) + final Method initializeScrollbars = + android.view.View.class.getDeclaredMethod("initializeScrollbars", + TypedArray.class); + initializeScrollbars.invoke(this, a); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + a.recycle(); } @@ -565,7 +585,7 @@ void invokeOnItemScrollListener() { * @return true if the scrolling cache is enabled, false otherwise * * @see #setScrollingCacheEnabled(boolean) - * @see View#setDrawingCacheEnabled(boolean) + * @see android.view.View#setDrawingCacheEnabled(boolean) */ @ViewDebug.ExportedProperty public boolean isScrollingCacheEnabled() { @@ -583,7 +603,7 @@ public boolean isScrollingCacheEnabled() { * @param enabled true to enable the scroll cache, false otherwise * * @see #isScrollingCacheEnabled() - * @see View#setDrawingCacheEnabled(boolean) + * @see android.view.View#setDrawingCacheEnabled(boolean) */ public void setScrollingCacheEnabled(boolean enabled) { if (mScrollingCacheEnabled && !enabled) { @@ -644,16 +664,6 @@ void requestLayoutIfNecessary() { } } - @Override - public void onRestoreInstanceState(Parcelable state) { - super.onRestoreInstanceState(state); - - DebugUtil.LogDebug("data changed by onRestoreInstanceState()"); - - mDataChanged = true; - requestLayout(); - } - @Override public void requestLayout() { if (!mBlockLayoutRequests && !mInLayout) { @@ -1527,7 +1537,7 @@ public boolean onTouchEvent(MotionEvent ev) { mPerformClick = new PerformClick(); } - final PLA_AbsListView.PerformClick performClick = mPerformClick; + final PerformClick performClick = mPerformClick; performClick.mChild = child; performClick.mClickMotionPosition = motionPosition; performClick.rememberWindowAttachCount(); @@ -2452,14 +2462,51 @@ protected void handleDataChanged() { if (mNeedSync) { // Update this first, since setNextSelectedPositionInt inspects it mNeedSync = false; + mPendingSync = null; + if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL || (mTranscriptMode == TRANSCRIPT_MODE_NORMAL && - mFirstPosition + getChildCount() >= mOldItemCount)) { + mFirstPosition + getChildCount() >= mOldItemCount)) { mLayoutMode = LAYOUT_FORCE_BOTTOM; return; } switch (mSyncMode) { + case SYNC_SELECTED_POSITION: + if (isInTouchMode()) { + // We saved our state when not in touch mode. (We know this because + // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to + // restore in touch mode. Just leave mSyncPosition as it is (possibly + // adjusting if the available range changed) and return. + mLayoutMode = LAYOUT_SYNC; + mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1); + + return; + } else { + // See if we can find a position in the new data with the same + // id as the old selection. This will change mSyncPosition. + newPos = findSyncPosition(); + if (newPos >= 0) { + // Found it. Now verify that new selection is still selectable + selectablePos = lookForSelectablePosition(newPos, true); + if (selectablePos == newPos) { + // Same row id is selected + mSyncPosition = newPos; + + if (mSyncHeight == getHeight()) { + // If we are at the same height as when we saved state, try + // to restore the scroll position too. + mLayoutMode = LAYOUT_SYNC; + } else { + // We are not the same height as when the selection was saved, so + // don't try to restore the exact position + mLayoutMode = LAYOUT_SET_SELECTION; + } + return; + } + } + } + break; case SYNC_FIRST_POSITION: // Leave mSyncPosition as it is -- just pin to available range mLayoutMode = LAYOUT_SYNC; @@ -2483,10 +2530,14 @@ protected void handleDataChanged() { // Make sure we select something selectable -- first look down selectablePos = lookForSelectablePosition(newPos, true); - // Looking down didn't work -- try looking up - selectablePos = lookForSelectablePosition(newPos, false); if (selectablePos >= 0) { return; + } else { + // Looking down didn't work -- try looking up + selectablePos = lookForSelectablePosition(newPos, false); + if (selectablePos >= 0) { + return; + } } } else { @@ -2500,19 +2551,23 @@ protected void handleDataChanged() { // Nothing is selected. Give up and reset everything. mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP; + mSelectedPosition = INVALID_POSITION; + mSelectedRowId = INVALID_ROW_ID; mNeedSync = false; + mPendingSync = null; + checkSelectionChanged(); } /** * adapter data is changed.. should keep current view layout information.. - * @param mSyncPosition + * @param syncPosition */ protected void onLayoutSync(int syncPosition) { } /** * adapter data is changed.. children layout manipulation is finished. - * @param mSyncPosition + * @param syncPosition */ protected void onLayoutSyncFinished(int syncPosition) { } @@ -2576,12 +2631,12 @@ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { - return new PLA_AbsListView.LayoutParams(getContext(), attrs); + return new LayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof PLA_AbsListView.LayoutParams; + return p instanceof LayoutParams; } /** @@ -2654,7 +2709,7 @@ public void reclaimViews(List views) { // Reclaim views on screen for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - PLA_AbsListView.LayoutParams lp = (PLA_AbsListView.LayoutParams) child.getLayoutParams(); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); // Don't reclaim header or footer views, or views that should be ignored if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) { views.add(child); @@ -2732,9 +2787,7 @@ public void reclaimViews(List views) { * associated to the View. * * @param listener The recycler listener to be notified of views set aside - * in the recycler. - * - * @see android.widget.PLA_AbsListView.RecycleBin + * in the recycler. * @see android.widget.AbsListView.RecyclerListener */ public void setRecyclerListener(RecyclerListener listener) { @@ -2801,7 +2854,6 @@ public LayoutParams(ViewGroup.LayoutParams source) { * inside the RecycleBin's scrap heap. This listener is used to free resources * associated to Views placed in the RecycleBin. * - * @see android.widget.PLA_AbsListView.RecycleBin * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) */ public static interface RecyclerListener { @@ -2926,7 +2978,7 @@ void fillActiveViews(int childCount, int firstActivePosition) { final View[] activeViews = mActiveViews; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - PLA_AbsListView.LayoutParams lp = (PLA_AbsListView.LayoutParams) child.getLayoutParams(); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); // Don't put header or footer views into the scrap heap if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. @@ -3002,7 +3054,7 @@ View getScrapView(int position) { void addScrapView(View scrap) { DebugUtil.i("addToScrap"); - PLA_AbsListView.LayoutParams lp = (PLA_AbsListView.LayoutParams) scrap.getLayoutParams(); + LayoutParams lp = (LayoutParams) scrap.getLayoutParams(); if (lp == null) { return; } @@ -3046,7 +3098,7 @@ void scrapActiveViews() { for (int i = count - 1; i >= 0; i--) { final View victim = activeViews[i]; if (victim != null) { - int whichScrap = ((PLA_AbsListView.LayoutParams) victim.getLayoutParams()).viewType; + int whichScrap = ((LayoutParams) victim.getLayoutParams()).viewType; activeViews[i] = null; @@ -3228,4 +3280,96 @@ protected int getScrollChildBottom() { return 0; return getChildAt(count - 1).getBottom(); } + + + static class SavedState { + long firstId; + int viewTop; + int position; + int height; + int childCount; + int[] viewTops; + } + + @Override + public Parcelable onSaveInstanceState() { + + Bundle ss = new Bundle(); + ss.putParcelable("instanceState", super.onSaveInstanceState()); + + if (mPendingSync != null) { + // Just keep what we last restored. + ss.putLong("firstId", mPendingSync.firstId); + ss.putInt("viewTop", mPendingSync.viewTop); + ss.putIntArray("viewTops", mPendingSync.viewTops); + ss.putInt("position", mPendingSync.position); + ss.putInt("height", mPendingSync.height); + ss.putInt("childCount", mPendingSync.childCount); + return ss; + } + + ss.putInt("height", getHeight()); + int childCount = getChildCount(); + ss.putInt("childCount", childCount); + boolean haveChildren = childCount > 0 && mItemCount > 0; + if (haveChildren && mFirstPosition > 0) { + // Remember the position of the first child. + // We only do this if we are not currently at the top of + // the list, for two reasons: + // (1) The list may be in the process of becoming empty, in + // which case mItemCount may not be 0, but if we try to + // ask for any information about position 0 we will crash. + // (2) Being "at the top" seems like a special case, anyway, + // and the user wouldn't expect to end up somewhere else when + // they revisit the list even if its content has changed. + + int firstPos = mFirstPosition; + if (firstPos >= mItemCount) { + firstPos = mItemCount - 1; + } + ss.putInt("position", firstPos); + ss.putLong("firstId", mAdapter.getItemId(firstPos)); + View v = getChildAt(0); + ss.putInt("viewTop", v.getTop()); + int[] viewTops = new int[childCount]; + for (int i = 0; i < childCount; i++) { + viewTops[i] = getChildAt(i).getTop(); + } + ss.putIntArray("viewTops", viewTops); + } else { + ss.putInt("viewTop", 0); + ss.putLong("firstId", INVALID_POSITION); + ss.putInt("position", 0); + ss.putIntArray("viewTops", new int[1]); + } + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + Bundle bundle = (Bundle) state; + mDataChanged = true; + mSyncHeight = bundle.getInt("height"); + long firstId = bundle.getLong("firstId"); + if (firstId >= 0) { + mNeedSync = true; + SavedState ss = new SavedState(); + ss.firstId = firstId; + ss.height = (int) mSyncHeight; + ss.position = bundle.getInt("position"); + ss.viewTop = bundle.getInt("viewTop"); + ss.childCount = bundle.getInt("childCount"); + ss.viewTops = bundle.getIntArray("viewTops"); + mPendingSync = ss; + mSyncRowId = ss.firstId; + mSyncPosition = ss.position; + mSpecificTop = ss.viewTop; + mSpecificTops = ss.viewTops; + } + state = bundle.getParcelable("instanceState"); + } + super.onRestoreInstanceState(state); + requestLayout(); + } }//end of class diff --git a/src/main/java/com/huewu/pla/lib/internal/PLA_AdapterView.java b/library/src/main/java/com/huewu/pla/lib/internal/PLA_AdapterView.java similarity index 98% rename from src/main/java/com/huewu/pla/lib/internal/PLA_AdapterView.java rename to library/src/main/java/com/huewu/pla/lib/internal/PLA_AdapterView.java index 8f66c9e..aa587a6 100644 --- a/src/main/java/com/huewu/pla/lib/internal/PLA_AdapterView.java +++ b/library/src/main/java/com/huewu/pla/lib/internal/PLA_AdapterView.java @@ -23,21 +23,19 @@ import android.os.SystemClock; import android.util.AttributeSet; import android.util.SparseArray; -import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.widget.Adapter; import com.huewu.pla.lib.DebugUtil; /** - * An AdapterView is a view whose children are determined by an {@link Adapter}. + * An AdapterView is a view whose children are determined by an {@link android.widget.Adapter}. * *

* See {@link ListView}, {@link GridView}, {@link Spinner} and @@ -46,13 +44,13 @@ public abstract class PLA_AdapterView extends ViewGroup { /** - * The item view type returned by {@link Adapter#getItemViewType(int)} when + * The item view type returned by {@link android.widget.Adapter#getItemViewType(int)} when * the adapter does not want the item's view recycled. */ public static final int ITEM_VIEW_TYPE_IGNORE = -1; /** - * The item view type returned by {@link Adapter#getItemViewType(int)} when + * The item view type returned by {@link android.widget.Adapter#getItemViewType(int)} when * the item is a header or footer. */ public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2; @@ -66,10 +64,11 @@ public abstract class PLA_AdapterView extends ViewGroup { /** * The offset in pixels from the top of the AdapterView to the top * of the view to select during the next layout. - */ - int mSpecificTop; - - /** + */ + int mSpecificTop; + int[] mSpecificTops; + + /** * Position from which to start looking for mSyncRowId */ int mSyncPosition; @@ -359,11 +358,11 @@ public final OnItemSelectedListener getOnItemSelectedListener() { /** * Extra menu information provided to the - * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) } + * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo) } * callback when a context menu is brought up for this AdapterView. * */ - public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo { + public static class AdapterContextMenuInfo implements ContextMenuInfo { public AdapterContextMenuInfo(View targetView, int position, long id) { this.targetView = targetView; diff --git a/src/main/java/com/huewu/pla/lib/internal/PLA_HeaderViewListAdapter.java b/library/src/main/java/com/huewu/pla/lib/internal/PLA_HeaderViewListAdapter.java similarity index 100% rename from src/main/java/com/huewu/pla/lib/internal/PLA_HeaderViewListAdapter.java rename to library/src/main/java/com/huewu/pla/lib/internal/PLA_HeaderViewListAdapter.java diff --git a/src/main/java/com/huewu/pla/lib/internal/PLA_ListView.java b/library/src/main/java/com/huewu/pla/lib/internal/PLA_ListView.java similarity index 95% rename from src/main/java/com/huewu/pla/lib/internal/PLA_ListView.java rename to library/src/main/java/com/huewu/pla/lib/internal/PLA_ListView.java index 95f6622..27f5f4c 100644 --- a/src/main/java/com/huewu/pla/lib/internal/PLA_ListView.java +++ b/library/src/main/java/com/huewu/pla/lib/internal/PLA_ListView.java @@ -32,7 +32,6 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.ListAdapter; -import android.widget.WrapperListAdapter; import com.huewu.pla.R; import com.huewu.pla.lib.DebugUtil; @@ -51,7 +50,7 @@ /** * A view that shows items in a vertically scrolling list. The items - * come from the {@link ListAdapter} associated with this view. + * come from the {@link android.widget.ListAdapter} associated with this view. * *

See the List View * tutorial.

@@ -90,14 +89,14 @@ public class PLA_ListView extends PLA_AbsListView { public class FixedViewInfo { /** The view to add to the list */ public View view; - /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */ + /** The data backing the view. This is returned from {@link android.widget.ListAdapter#getItem(int)}. */ public Object data; /** true if the fixed view should be selectable in the list */ public boolean isSelectable; } - private ArrayList mHeaderViewInfos = new ArrayList(); - private ArrayList mFooterViewInfos = new ArrayList(); + private ArrayList mHeaderViewInfos = new ArrayList(); + private ArrayList mFooterViewInfos = new ArrayList(); Drawable mDivider; int mDividerHeight; @@ -119,6 +118,7 @@ public class FixedViewInfo { // used for temporary calculations. private final Rect mTempRect = new Rect(); private Paint mDividerPaint; + private Paint mContentPaint = new Paint(); public PLA_ListView(Context context) { this(context, null); @@ -162,6 +162,12 @@ public PLA_ListView(Context context, AttributeSet attrs, int defStyle) { mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true); mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true); + if (a.hasValue(R.styleable.ListView_plaContentBackground)) { + int contentBackgroundColor = a.getColor(R.styleable.ListView_plaContentBackground, 0); + mContentPaint = new Paint(); + mContentPaint.setColor(contentBackgroundColor); + } + a.recycle(); } @@ -316,7 +322,7 @@ public boolean isFixedView( View v ) { public boolean removeHeaderView(View v) { if (mHeaderViewInfos.size() > 0) { boolean result = false; - if (((PLA_HeaderViewListAdapter) mAdapter).removeHeader(v)) { + if (mAdapter != null && ((PLA_HeaderViewListAdapter) mAdapter).removeHeader(v)) { mDataSetObserver.onChanged(); result = true; } @@ -393,7 +399,7 @@ public int getFooterViewsCount() { public boolean removeFooterView(View v) { if (mFooterViewInfos.size() > 0) { boolean result = false; - if (((PLA_HeaderViewListAdapter) mAdapter).removeFooter(v)) { + if (mAdapter != null && ((PLA_HeaderViewListAdapter) mAdapter).removeFooter(v)) { mDataSetObserver.onChanged(); result = true; } @@ -405,12 +411,12 @@ public boolean removeFooterView(View v) { /** * Returns the adapter currently in use in this ListView. The returned adapter - * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but - * might be a {@link WrapperListAdapter}. + * might not be the same adapter passed to {@link #setAdapter(android.widget.ListAdapter)} but + * might be a {@link android.widget.WrapperListAdapter}. * * @return The adapter currently used to display data in this ListView. * - * @see #setAdapter(ListAdapter) + * @see #setAdapter(android.widget.ListAdapter) */ @Override public ListAdapter getAdapter() { @@ -420,7 +426,7 @@ public ListAdapter getAdapter() { /** * Sets the data behind this ListView. * - * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter}, + * The adapter passed to this method may be wrapped by a {@link android.widget.WrapperListAdapter}, * depending on the ListView features currently in use. For instance, adding * headers and/or footers will cause the adapter to be wrapped. * @@ -647,12 +653,11 @@ protected int getItemBottom(int pos) { protected void fillGap(boolean down) { final int count = getChildCount(); if (down) { - fillDown(mFirstPosition + count, getItemTop(mFirstPosition + count)); - onAdjustChildViews( down ); + fillDown(mFirstPosition + count, getFillChildBottom() + mDividerHeight); } else { - fillUp(mFirstPosition - 1, getItemBottom(mFirstPosition - 1)); - onAdjustChildViews( down ); + fillUp(mFirstPosition - 1, getFillChildTop()); } + onAdjustChildViews(down); } /** @@ -666,21 +671,16 @@ protected void fillGap(boolean down) { * @return The view that is currently selected, if it happens to be in the * range that we draw. */ - private View fillDown(int pos, int top) { - + private View fillDown(int pos, int nextTop) { DebugUtil.i("fill down: " + pos); - //int end = (mBottom - mTop) - mListPadding.bottom; - int end = (getBottom() - getTop()) - mListPadding.bottom; - int childTop = getFillChildBottom() + mDividerHeight; - - while (childTop < end && pos < mItemCount) { + int end = getBottom() - getTop() - mListPadding.bottom; + while (nextTop < end && pos < mItemCount) { // is this the selected item? makeAndAddView(pos, getItemTop(pos), true, false); pos++; - childTop = getFillChildBottom() + mDividerHeight; + nextTop = getFillChildBottom() + mDividerHeight; } - return null; } @@ -694,22 +694,20 @@ private View fillDown(int pos, int top) { * * @return The view that is currently selected */ - private View fillUp(int pos, int bottom) { + private View fillUp(int pos, int nextBottom) { DebugUtil.i("fill up: " + pos); int end = mListPadding.top; - int childBottom = getFillChildTop(); - while (childBottom > end && pos >= 0) { + while (nextBottom > end && pos >= 0) { // is this the selected item? makeAndAddView(pos, getItemBottom(pos), false, false); // nextBottom = child.getTop() - mDividerHeight; pos--; - childBottom = getItemBottom(pos); + nextBottom = getItemBottom(pos); } mFirstPosition = pos + 1; - return null; } @@ -817,7 +815,7 @@ protected boolean recycleOnMeasure() { * current height reaches maxHeight. * * @param widthMeasureSpec The width measure spec to be given to a child's - * {@link View#measure(int, int)}. + * {@link android.view.View#measure(int, int)}. * @param startPosition The position of the first child to be shown. * @param endPosition The (inclusive) position of the last child to be * shown. Specify {@link #NO_POSITION} if the last child should be @@ -940,16 +938,15 @@ private View fillSpecific(int position, int top) { DebugUtil.i("fill specific: " + position + ":" + top); View temp = makeAndAddView(position, top, true, false); - // Possibly changed again in fillUp if we add rows above this one. - mFirstPosition = position; + final int dividerHeight = mDividerHeight; if (!mStackFromBottom) { - fillUp(position - 1, temp.getTop() - dividerHeight); + fillUp(position - 1, getFillChildTop()); // This will correct for the top of the first view not touching the top of the list adjustViewsUpOrDown(); - fillDown(position + 1, temp.getBottom() + dividerHeight); + fillDown(position + 1, getFillChildBottom() + mDividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooHigh(childCount); @@ -958,7 +955,7 @@ private View fillSpecific(int position, int top) { fillDown(position + 1, temp.getBottom() + dividerHeight); // This will correct for the bottom of the last view not touching the bottom of the list adjustViewsUpOrDown(); - fillUp(position - 1, temp.getTop() - dividerHeight); + fillUp(position - 1, getFillChildTop()); int childCount = getChildCount(); if (childCount > 0) { correctTooLow(childCount); @@ -1001,7 +998,7 @@ private void correctTooHigh(int childCount) { // Make sure we are 1) Too high, and 2) Either there are more rows above the // first row or the first row is scrolled off the top of the drawable area - if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) { + if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) { if (mFirstPosition == 0) { // Don't pull the top too far down bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop); @@ -1013,7 +1010,7 @@ private void correctTooHigh(int childCount) { // Fill the gap that was opened above mFirstPosition with more rows, if // possible int newFirstTop = getScrollChildTop(); - fillUp(mFirstPosition - 1, newFirstTop - mDividerHeight); + fillUp(mFirstPosition - 1, getFillChildTop()); // Close up the remaining gap adjustViewsUpOrDown(); } @@ -1041,7 +1038,7 @@ private void correctTooLow(int childCount) { final int start = mListPadding.top; // This is bottom of our drawable area - final int end = (getBottom() -getTop()) - mListPadding.bottom; + final int end = (getBottom() - getTop()) - mListPadding.bottom; // This is how far the top edge of the first view is from the top of the drawable area int topOffset = firstTop - start; @@ -1052,7 +1049,7 @@ private void correctTooLow(int childCount) { // Make sure we are 1) Too low, and 2) Either there are more rows below the // last row or the last row is scrolled off the bottom of the drawable area if (topOffset > 0) { - if (lastPosition < mItemCount - 1 || lastBottom > end) { + if (lastPosition < mItemCount - 1 || lastBottom > end) { if (lastPosition == mItemCount - 1) { // Don't pull the bottom too far up topOffset = Math.min(topOffset, lastBottom - end); @@ -1062,7 +1059,7 @@ private void correctTooLow(int childCount) { if (lastPosition < mItemCount - 1) { // Fill the gap that was opened below the last position with more rows, if // possible - fillDown(lastPosition + 1, getFillChildTop() + mDividerHeight); + fillDown(lastPosition + 1, getFillChildBottom() + mDividerHeight); // Close up the remaining gap adjustViewsUpOrDown(); } @@ -1091,9 +1088,8 @@ protected void layoutChildren() { return; } - int childrenTop = mListPadding.top; - //int childrenBottom = mBottom - mTop - mListPadding.bottom; - int childrenBottom = getBottom() - getTop() - mListPadding.bottom; + int childrenTop = getFillChildBottom() + mDividerHeight; + int childrenBottom = getFillChildTop(); int childCount = getChildCount(); int index = 0; @@ -1157,7 +1153,12 @@ protected void layoutChildren() { onLayoutSync(mSyncPosition); // Clear out old views detachAllViewsFromParent(); - fillSpecific(mSyncPosition, mSpecificTop); + if (mSpecificTops != null) { + fillSynced(mSyncPosition, mSpecificTops); + mSpecificTops = null; + } else { + fillSpecific(mSyncPosition, mSpecificTop); + } onLayoutSyncFinished(mSyncPosition); break; case LAYOUT_FORCE_BOTTOM: @@ -1226,6 +1227,14 @@ protected void layoutChildren() { } } + private void fillSynced(int position, int[] specificTops) { + for (int i = 0; i < specificTops.length; i++) { + makeAndAddView(position + i, specificTops[i], true, false); + adjustViewsUpOrDown(); + } + mFirstPosition = position; + } + /** * Obtain the view and add it to our list of children. The view can be made * fresh, converted from an unused view, or used as is if it was in the @@ -1235,7 +1244,6 @@ protected void layoutChildren() { * @param childrenBottomOrTop Top or bottom edge of the view to add * @param flow If flow is true, align top edge to y. If false, align bottom * edge to y. - * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @return View that was added */ @@ -1516,7 +1524,7 @@ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { * Go to the last or first item if possible (not worrying about panning across or navigating * within the internal focus of the currently selected item.) * - * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} + * @param direction either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN} * * @return whether selection was moved */ @@ -1748,6 +1756,8 @@ protected void dispatchDraw(Canvas canvas) { final boolean drawOverscrollFooter = overscrollFooter != null; final boolean drawDividers = dividerHeight > 0 && mDivider != null; + drawContentBackground(canvas); + if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) { // Only modify the top and bottom in the loop, we set the left and right here final Rect bounds = mTempRect; @@ -1888,6 +1898,31 @@ protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); } + private void drawContentBackground(Canvas canvas) { + if (getHeaderViewsCount() > 0) { + Rect rect = mTempRect; + rect.left = getLeft(); + rect.right = getRight(); + View firstVisibleView = getChildAt(getFirstVisiblePosition()); + + if (isHeaderView(firstVisibleView)) { + View lastHeader = mHeaderViewInfos.get(mHeaderViewInfos.size() - 1).view; + rect.top = lastHeader.getBottom(); + } else + rect.top = 0; + rect.bottom = getBottom(); + canvas.drawRect(rect, mContentPaint); + } + } + + private boolean isHeaderView(View view) { + for (FixedViewInfo info : mHeaderViewInfos) { + if (info.view == view) + return true; + } + return false; + } + /** * Draws a divider for the given child in the given bounds. * @@ -1953,7 +1988,7 @@ public int getDividerHeight() { /** * Sets the height of the divider that will be drawn between each item in the list. Calling - * this will override the intrinsic height as set by {@link #setDivider(Drawable)} + * this will override the intrinsic height as set by {@link #setDivider(android.graphics.drawable.Drawable)} * * @param height The new height of the divider in pixels. */ @@ -2124,7 +2159,7 @@ public boolean performItemClick(View view, int position, long id) { * Sets the checked state of the specified position. The is only valid if * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or * {@link #CHOICE_MODE_MULTIPLE}. - * + * * @param position The item whose checked state is to be checked * @param value The new checked state for the item */ @@ -2175,10 +2210,10 @@ public SparseBooleanArray getCheckedItemPositions() { /** * Returns the set of checked items ids. The result is only valid if the * choice mode has not been set to {@link #CHOICE_MODE_NONE}. - * + * * @return A new array which contains the id of each checked item in the * list. - * + * * @deprecated Use {@link #getCheckedItemIds()} instead. */ @Deprecated @@ -2194,8 +2229,8 @@ public long[] getCheckItemIds() { /** * Returns the set of checked items ids. The result is only valid if the * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter - * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true}) - * + * has stable IDs. ({@link android.widget.ListAdapter#hasStableIds()} == {@code true}) + * * @return A new array which contains the id of each checked item in the * list. */ diff --git a/res/drawable-hdpi/ad_button_015_refresh_down_arrow.png b/library/src/main/res/drawable-hdpi/ad_button_015_refresh_down_arrow.png similarity index 100% rename from res/drawable-hdpi/ad_button_015_refresh_down_arrow.png rename to library/src/main/res/drawable-hdpi/ad_button_015_refresh_down_arrow.png diff --git a/res/drawable-hdpi/arrow.png b/library/src/main/res/drawable-hdpi/arrow.png similarity index 100% rename from res/drawable-hdpi/arrow.png rename to library/src/main/res/drawable-hdpi/arrow.png diff --git a/res/drawable-hdpi/updating.png b/library/src/main/res/drawable-hdpi/updating.png similarity index 100% rename from res/drawable-hdpi/updating.png rename to library/src/main/res/drawable-hdpi/updating.png diff --git a/res/drawable-hdpi/updating_spinner.xml b/library/src/main/res/drawable-hdpi/updating_spinner.xml similarity index 100% rename from res/drawable-hdpi/updating_spinner.xml rename to library/src/main/res/drawable-hdpi/updating_spinner.xml diff --git a/res/drawable-mdpi/ad_button_015_refresh_down_arrow.png b/library/src/main/res/drawable-mdpi/ad_button_015_refresh_down_arrow.png similarity index 100% rename from res/drawable-mdpi/ad_button_015_refresh_down_arrow.png rename to library/src/main/res/drawable-mdpi/ad_button_015_refresh_down_arrow.png diff --git a/res/drawable-xhdpi/ad_button_015_refresh_down_arrow.png b/library/src/main/res/drawable-xhdpi/ad_button_015_refresh_down_arrow.png similarity index 100% rename from res/drawable-xhdpi/ad_button_015_refresh_down_arrow.png rename to library/src/main/res/drawable-xhdpi/ad_button_015_refresh_down_arrow.png diff --git a/res/layout/pull_to_refresh_header.xml b/library/src/main/res/layout/pull_to_refresh_header.xml similarity index 100% rename from res/layout/pull_to_refresh_header.xml rename to library/src/main/res/layout/pull_to_refresh_header.xml diff --git a/res/values/attrs.xml b/library/src/main/res/values/attrs.xml similarity index 99% rename from res/values/attrs.xml rename to library/src/main/res/values/attrs.xml index e0d6cfd..78068d1 100644 --- a/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -838,6 +838,8 @@ + + \ No newline at end of file diff --git a/res/values/dimens.xml b/library/src/main/res/values/dimens.xml similarity index 100% rename from res/values/dimens.xml rename to library/src/main/res/values/dimens.xml diff --git a/res/values/ids.xml b/library/src/main/res/values/ids.xml similarity index 100% rename from res/values/ids.xml rename to library/src/main/res/values/ids.xml diff --git a/res/values/strings.xml b/library/src/main/res/values/strings.xml similarity index 100% rename from res/values/strings.xml rename to library/src/main/res/values/strings.xml diff --git a/lint.xml b/lint.xml deleted file mode 100644 index ee0eead..0000000 --- a/lint.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 6447511..0000000 --- a/pom.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - 4.0.0 - - com.huewu.pla - pla - 1.02 - apklib - - Vingle Android App - - http://www.vingle.net - - - UTF-8 - UTF-8 - - - 1.6 - - 4.1.1.4 - 17 - - - - https://github.com/balmbees/PinterestLikeAdapterView.git - scm:git@github.com:balmbees/PinterestLikeAdapterView.git - scm:git@github.com:balmbees/PinterestLikeAdapterView.git - - - - GitHub Issues - https://github.com/balmbees/PinterestLikeAdapterView/issues - - - - Vingle - http://www.vingle.net/ - - - - - repo - https://github.com/balmbees/vingle-maven-repo/raw/PLA/master/releases - - - snapshot-repo - https://github.com/balmbees/vingle-maven-repo/raw/PLA/master/snapshots - - - - - - com.jayway.maven.plugins.android.generation2 - android-maven-plugin - true - 3.5.3 - - ignored - - ${android.platform} - - - - - - - - pusher-java-client-mvn-repo - Pusher Java Client Library - https://raw.github.com/pusher/pusher-java-client/mvn-repo/ - - - true - - - true - - - - - - - - com.google.android - android - ${android.version} - provided - - - - diff --git a/proguard-project.txt b/proguard-project.txt deleted file mode 100644 index f2fe155..0000000 --- a/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/project.properties b/project.properties deleted file mode 100644 index 484dab0..0000000 --- a/project.properties +++ /dev/null @@ -1,15 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-17 -android.library=true diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index f170ae5..0000000 Binary files a/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/res/layout/sample_pull_to_refresh_act.xml b/res/layout/sample_pull_to_refresh_act.xml deleted file mode 100644 index 1c045fe..0000000 --- a/res/layout/sample_pull_to_refresh_act.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml deleted file mode 100644 index d408cbc..0000000 --- a/res/values-v11/styles.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - + + diff --git a/screenshot.png b/screenshot.png index 726acc2..cbda31f 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..c504e93 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include ':sample' +include ':library' diff --git a/src/main/java/com/huewu/pla/lib/MultiColumnPullToRefreshListView.java b/src/main/java/com/huewu/pla/lib/MultiColumnPullToRefreshListView.java deleted file mode 100644 index e10bfed..0000000 --- a/src/main/java/com/huewu/pla/lib/MultiColumnPullToRefreshListView.java +++ /dev/null @@ -1,783 +0,0 @@ - -package com.huewu.pla.lib; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Typeface; -import android.os.Handler; -import android.os.Message; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.LinearInterpolator; -import android.view.animation.RotateAnimation; -import android.view.animation.TranslateAnimation; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.huewu.pla.R; - -import java.lang.ref.WeakReference; -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * A generic, customizable Android ListView implementation that has 'Pull to - * Refresh' functionality. - *

- * This ListView can be used in place of the normal Android - * android.widget.ListView class. - *

- * Users of this class should implement OnRefreshListener and call - * setOnRefreshListener(..) to get notified on refresh events. The using class - * should call onRefreshComplete() when refreshing is finished. - *

- * The using class can call setRefreshing() to set the state explicitly to - * refreshing. This is useful when you want to show the spinner and 'Refreshing' - * text when the refresh was not triggered by 'Pull to Refresh', for example on - * start. - *

- * For more information, visit the project page: - * https://github.com/erikwt/PullToRefresh-ListView - * - * @author Erik Wallentinsen - * @version 1.0.0 - */ -public class MultiColumnPullToRefreshListView extends MultiColumnListView { - - private static final float PULL_RESISTANCE = 3.0f; - private static final int BOUNCE_ANIMATION_DURATION = 215; - private static final int BOUNCE_ANIMATION_DELAY = 20; - private static final int ROTATE_ARROW_ANIMATION_DURATION = 250; - - // Loading... - private LoadingThread mLoadingThread = null; - final static int LOADINGBUFFER = 400; - final static int LOADINGZERO = 100; - final static int LOADINGONE = 101; - final static int LOADINGTWO = 102; - final static int LOADINGTHREE = 103; - - private static enum State { - PULL_TO_REFRESH, - RELEASE_TO_REFRESH, - REFRESHING - } - - /** - * Interface to implement when you want to get notified of 'pull to refresh' - * events. Call setOnRefreshListener(..) to activate an OnRefreshListener. - */ - public interface OnRefreshListener { - - /** - * Method to be called when a refresh is requested - */ - public void onRefresh(); - } - - private static int measuredHeaderHeight; - - private boolean scrollbarEnabled; - private boolean bounceBackHeader; - private boolean lockScrollWhileRefreshing; - private boolean showLastUpdatedText; - private String pullToRefreshText; - private String releaseToRefreshText; - private String refreshingText; - private String lastUpdatedText; - private SimpleDateFormat lastUpdatedDateFormat = new SimpleDateFormat("dd/MM HH:mm"); - - private float previousY; - private int headerPadding; - private boolean hasResetHeader; - private long lastUpdated = -1; - private State state; - private LinearLayout headerContainer; - private RelativeLayout header; - private RotateAnimation flipAnimation; - private RotateAnimation reverseFlipAnimation; - private ImageView image; - private ProgressBar spinner; - private TextView text; - // private TextView loadingText; - private TextView lastUpdatedTextView; - private OnRefreshListener onRefreshListener; - private TranslateAnimation bounceAnimation; - - private boolean isHeaderRefreshing = false; - private boolean isHeaderShowing = false; - - public MultiColumnPullToRefreshListView(Context context) { - super(context); - init(context, null); - } - - public MultiColumnPullToRefreshListView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } - - public MultiColumnPullToRefreshListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(context, attrs); - } - - /** - * Activate an OnRefreshListener to get notified on 'pull to refresh' - * events. - * - * @param onRefreshListener The OnRefreshListener to get notified - */ - public void setOnRefreshListener(OnRefreshListener onRefreshListener) { - this.onRefreshListener = onRefreshListener; - } - - /** - * @return If the list is in 'Refreshing' state - */ - public boolean isRefreshing() { - return state == State.REFRESHING; - } - - /** - * Default is false. When lockScrollWhileRefreshing is set to true, the list - * cannot scroll when in 'refreshing' mode. It's 'locked' on refreshing. - * - * @param lockScrollWhileRefreshing - */ - public void setLockScrollWhileRefreshing(boolean lockScrollWhileRefreshing) { - this.lockScrollWhileRefreshing = lockScrollWhileRefreshing; - } - - /** - * Default is false. Show the last-updated date/time in the 'Pull ro - * Refresh' header. See 'setLastUpdatedDateFormat' to set the date/time - * formatting. - * - * @param showLastUpdatedText - */ - public void setShowLastUpdatedText(boolean showLastUpdatedText) { - this.showLastUpdatedText = showLastUpdatedText; - if (!showLastUpdatedText) - lastUpdatedTextView.setVisibility(View.GONE); - } - - /** - * Default: "dd/MM HH:mm". Set the format in which the last-updated - * date/time is shown. Meaningless if 'showLastUpdatedText == false - * (default)'. See 'setShowLastUpdatedText'. - * - * @param lastUpdatedDateFormat - */ - public void setLastUpdatedDateFormat(SimpleDateFormat lastUpdatedDateFormat) { - this.lastUpdatedDateFormat = lastUpdatedDateFormat; - } - - /** - * Explicitly set the state to refreshing. This is useful when you want to - * show the spinner and 'Refreshing' text when the refresh was not triggered - * by 'pull to refresh', for example on start. - */ - public void setRefreshing() { - state = State.REFRESHING; - setUiRefreshing(); - // setHeaderPadding(0); - // scrollTo(0, 0); - } - - /** - * Set the state back to 'pull to refresh'. Call this method when refreshing - * the data is finished. - */ - public void onRefreshComplete() { - state = State.PULL_TO_REFRESH; - resetHeader(); - lastUpdated = System.currentTimeMillis(); - } - - /** - * Change the label text on state 'Pull to Refresh' - * - * @param pullToRefreshText Text - */ - public void setTextPullToRefresh(String pullToRefreshText) { - this.pullToRefreshText = pullToRefreshText; - if (state == State.PULL_TO_REFRESH) { - text.setText(pullToRefreshText); - image.setVisibility(VISIBLE); - if (mLoadingThread != null) { - mLoadingThread.interrupt(); - mLoadingThread = null; - } - isHeaderRefreshing = false; - // loadingText.setVisibility(View.GONE); - } - } - - /** - * Change the label text on state 'Release to Refresh' - * - * @param releaseToRefreshText Text - */ - public void setTextReleaseToRefresh(String releaseToRefreshText) { - this.releaseToRefreshText = releaseToRefreshText; - if (state == State.RELEASE_TO_REFRESH) { - text.setText(releaseToRefreshText); - image.setVisibility(VISIBLE); - if (mLoadingThread != null) { - mLoadingThread.interrupt(); - mLoadingThread = null; - } - isHeaderRefreshing = false; - // loadingText.setVisibility(View.GONE); - } - } - - /** - * Change the label text on state 'Refreshing' - * - * @param refreshingText Text - */ - public void setTextRefreshing(String refreshingText) { - this.refreshingText = refreshingText; - if (state == State.REFRESHING) { - text.setText(refreshingText); - image.setVisibility(INVISIBLE); - mLoadingThread = new LoadingThread(mLoadingHandler); - mLoadingThread.start(); - isHeaderRefreshing = true; - // loadingText.setVisibility(View.VISIBLE); - } - } - - public static float getDimensionDpSize(int id, Context context, AttributeSet attrs) { - TypedArray typedArray = context - .obtainStyledAttributes(attrs, R.styleable.PullToRefreshView); - Resources resources = context.getResources(); - DisplayMetrics metrics = resources.getDisplayMetrics(); - float dp = typedArray.getDimension(id, -1) / (metrics.densityDpi / 160f); - return dp; - } - - private void init(Context context, AttributeSet attrs) { - setVerticalFadingEdgeEnabled(false); - - headerContainer = (LinearLayout) LayoutInflater.from(getContext()).inflate( - R.layout.pull_to_refresh_header, null); - header = (RelativeLayout) headerContainer.findViewById(R.id.ptr_id_header); - text = (TextView) header.findViewById(R.id.ptr_id_text); - // loadingText = (TextView) - // header.findViewById(R.id.ptr_id_loading_text); - lastUpdatedTextView = (TextView) header.findViewById(R.id.ptr_id_last_updated); - image = (ImageView) header.findViewById(R.id.ptr_id_arrow); - spinner = (ProgressBar) header.findViewById(R.id.ptr_id_spinner); - - if (attrs == null) { - text.setTextSize(15); - // loadingText.setTextSize(15); - // loadingText.setLayoutParams(new - // android.view.ViewGroup.LayoutParams( - // (int) - // getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize, - // context, - // attrs), android.view.ViewGroup.LayoutParams.MATCH_PARENT)); - lastUpdatedTextView.setTextSize(12); - image.setPadding(0, 0, 5, 0); - spinner.setPadding(0, 0, 5, 0); - } else { - text.setTextSize(getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize, context, - attrs)); - // loadingText.setTextSize(getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize, - // context, attrs)); - // loadingText.setLayoutParams(new - // android.view.ViewGroup.LayoutParams( - // (int) - // getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize, - // context, - // attrs), android.view.ViewGroup.LayoutParams.MATCH_PARENT)); - lastUpdatedTextView.setTextSize(getDimensionDpSize( - R.styleable.PullToRefreshView_ptrLastUpdateTextSize, context, attrs)); - image.setPadding( - 0, - 0, - (int) getDimensionDpSize(R.styleable.PullToRefreshView_ptrArrowMarginRight, - context, attrs), 0); - spinner.setPadding( - 0, - 0, - (int) getDimensionDpSize(R.styleable.PullToRefreshView_ptrSpinnerMarginRight, - context, attrs), 0); - } - - TextView tv = new TextView(context); - tv.setText("Loading"); - tv.setTypeface(Typeface.DEFAULT_BOLD); - tv.setTextSize(getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize, context, attrs)); - - pullToRefreshText = getContext().getString(R.string.ptr_pull_to_refresh); - releaseToRefreshText = getContext().getString(R.string.ptr_release_to_refresh); - refreshingText = getContext().getString(R.string.ptr_loading); - lastUpdatedText = getContext().getString(R.string.ptr_last_updated); - - flipAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, - RotateAnimation.RELATIVE_TO_SELF, 0.5f); - flipAnimation.setInterpolator(new LinearInterpolator()); - flipAnimation.setDuration(ROTATE_ARROW_ANIMATION_DURATION); - flipAnimation.setFillAfter(true); - - reverseFlipAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, - RotateAnimation.RELATIVE_TO_SELF, 0.5f); - reverseFlipAnimation.setInterpolator(new LinearInterpolator()); - reverseFlipAnimation.setDuration(ROTATE_ARROW_ANIMATION_DURATION); - reverseFlipAnimation.setFillAfter(true); - - addHeaderView(headerContainer); - setState(State.PULL_TO_REFRESH); - scrollbarEnabled = isVerticalScrollBarEnabled(); - - ViewTreeObserver vto = header.getViewTreeObserver(); - vto.addOnGlobalLayoutListener(new PTROnGlobalLayoutListener()); - - // super.setOnItemClickListener(new PTROnItemClickListener()); - // super.setOnItemLongClickListener(new PTROnItemLongClickListener()); - } - - private void setHeaderPadding(int padding) { - headerPadding = padding; - - MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) header.getLayoutParams(); - mlp.setMargins(0, Math.round(padding), 0, 0); - header.setLayoutParams(mlp); - } - - private boolean isPulling = false; - - private boolean isPull(MotionEvent event) { - return isPulling; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - // Log.i("Vingle", "interceptEvent x : " + event.getX() + ", y : " + - // event.getY()); - // MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) - // header.getLayoutParams(); - // Log.i("Vingle", "interceptEvent hx : " + mlp.topMargin); - // Log.i("Vingle", "isHeaderRefresing : " + isHeaderRefreshing); - - if (isHeaderRefreshing && isHeaderShowing) { - - } - - if (lockScrollWhileRefreshing - && (state == State.REFRESHING || getAnimation() != null - && !getAnimation().hasEnded())) { - return true; // consume touch event here.. - } - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - if (getFirstVisiblePosition() == 0) - previousY = event.getY(); - break; - case MotionEvent.ACTION_MOVE: - if (getFirstVisiblePosition() == 0 && event.getY() - previousY > 0) { - isPulling = true; - return true; - } else { - isPulling = false; - } - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - isPulling = false; - break; - } - - return super.onInterceptTouchEvent(event); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - // Log.i("Vingle", "Event x : " + event.getX() + ", y : " + - // event.getY()); - // MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) - // header.getLayoutParams(); - // Log.i("Vingle", "Event hx : " + mlp.topMargin); - // Log.i("Vingle", "isHeaderRefresing : " + isHeaderRefreshing); - - if (isHeaderRefreshing && isHeaderShowing) { - - } - - if (lockScrollWhileRefreshing - && (state == State.REFRESHING || getAnimation() != null - && !getAnimation().hasEnded())) { - return true; - } - - switch (event.getAction()) { - - case MotionEvent.ACTION_UP: - if (isPull(event) - && (state == State.RELEASE_TO_REFRESH || getFirstVisiblePosition() == 0)) { - switch (state) { - case RELEASE_TO_REFRESH: - setState(State.REFRESHING); - bounceBackHeader(); - break; - case PULL_TO_REFRESH: - resetHeader(); - break; - default: - break; - } - } - break; - - case MotionEvent.ACTION_MOVE: - if (isPull(event)) { - float y = event.getY(); - float diff = y - previousY; - if (diff > 0) - diff /= PULL_RESISTANCE; - previousY = y; - - int newHeaderPadding = Math.max(Math.round(headerPadding + diff), - -header.getHeight()); - - if (newHeaderPadding != headerPadding && state != State.REFRESHING) { - setHeaderPadding(newHeaderPadding); - - if (state == State.PULL_TO_REFRESH && headerPadding > 0) { - setState(State.RELEASE_TO_REFRESH); - - image.clearAnimation(); - image.startAnimation(flipAnimation); - } else if (state == State.RELEASE_TO_REFRESH && headerPadding < 0) { - setState(State.PULL_TO_REFRESH); - - image.clearAnimation(); - image.startAnimation(reverseFlipAnimation); - } - } - } - - break; - } - - return super.onTouchEvent(event); - } - - private void bounceBackHeader() { - int yTranslate = state == State.REFRESHING ? - header.getHeight() - headerContainer.getHeight() : - -headerContainer.getHeight() - headerContainer.getTop(); - - bounceAnimation = new TranslateAnimation( - TranslateAnimation.ABSOLUTE, 0, - TranslateAnimation.ABSOLUTE, 0, - TranslateAnimation.ABSOLUTE, 0, - TranslateAnimation.ABSOLUTE, yTranslate); - - bounceAnimation.setDuration(BOUNCE_ANIMATION_DURATION); - bounceAnimation.setFillEnabled(true); - bounceAnimation.setFillAfter(false); - bounceAnimation.setFillBefore(true); - // bounceAnimation.setInterpolator(new - // OvershootInterpolator(BOUNCE_OVERSHOOT_TENSION)); - bounceAnimation.setAnimationListener(new HeaderAnimationListener(yTranslate)); - startAnimation(bounceAnimation); - } - - private void resetHeader() { - if (getFirstVisiblePosition() > 0) { - setHeaderPadding(-header.getHeight()); - setState(State.PULL_TO_REFRESH); - return; - } - - if (getAnimation() != null && !getAnimation().hasEnded()) { - bounceBackHeader = true; - } else { - bounceBackHeader(); - } - } - - private void setUiRefreshing() { - spinner.setVisibility(View.GONE); - image.clearAnimation(); - image.setVisibility(View.INVISIBLE); - text.setText(refreshingText); - mLoadingThread = new LoadingThread(mLoadingHandler); - mLoadingThread.start(); - isHeaderRefreshing = true; - // loadingText.setVisibility(View.VISIBLE); - } - - private void setState(State state) { - this.state = state; - switch (state) { - case PULL_TO_REFRESH: - spinner.setVisibility(View.GONE); - image.setVisibility(View.VISIBLE); - text.setText(pullToRefreshText); - if (mLoadingThread != null) { - mLoadingThread.interrupt(); - mLoadingThread = null; - } - isHeaderRefreshing = false; - // loadingText.setVisibility(View.GONE); - - if (showLastUpdatedText && lastUpdated != -1) { - lastUpdatedTextView.setVisibility(View.VISIBLE); - lastUpdatedTextView.setText(String.format(lastUpdatedText, - lastUpdatedDateFormat.format(new Date(lastUpdated)))); - } - - break; - - case RELEASE_TO_REFRESH: - spinner.setVisibility(View.GONE); - image.setVisibility(View.VISIBLE); - text.setText(releaseToRefreshText); - if (mLoadingThread != null) { - mLoadingThread.interrupt(); - mLoadingThread = null; - } - // loadingText.setVisibility(View.GONE); - isHeaderRefreshing = false; - break; - - case REFRESHING: - setUiRefreshing(); - - lastUpdated = System.currentTimeMillis(); - if (onRefreshListener == null) { - setState(State.PULL_TO_REFRESH); - } else { - onRefreshListener.onRefresh(); - } - - break; - } - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - - Log.i("Vingle", "hasResetHeader : " + hasResetHeader + ", t : " + t + ", oldt : " + oldt); - - if (!hasResetHeader) { - if (measuredHeaderHeight > 0 && state != State.REFRESHING) { - setHeaderPadding(-measuredHeaderHeight); - } - - hasResetHeader = true; - } - } - - private class HeaderAnimationListener implements AnimationListener { - - private int height, translation; - private State stateAtAnimationStart; - - public HeaderAnimationListener(int translation) { - this.translation = translation; - } - - @Override - public void onAnimationStart(Animation animation) { - stateAtAnimationStart = state; - - android.view.ViewGroup.LayoutParams lp = getLayoutParams(); - height = lp.height; - lp.height = getHeight() - translation; - setLayoutParams(lp); - - if (scrollbarEnabled) { - setVerticalScrollBarEnabled(false); - } - } - - @Override - public void onAnimationEnd(Animation animation) { - setHeaderPadding(stateAtAnimationStart == State.REFRESHING ? 0 : -measuredHeaderHeight - - headerContainer.getTop()); - // setSelection(0); - - android.view.ViewGroup.LayoutParams lp = getLayoutParams(); - lp.height = height; - setLayoutParams(lp); - - if (scrollbarEnabled) { - setVerticalScrollBarEnabled(true); - } - - if (bounceBackHeader) { - bounceBackHeader = false; - - postDelayed(new Runnable() { - - @Override - public void run() { - resetHeader(); - } - }, BOUNCE_ANIMATION_DELAY); - } else if (stateAtAnimationStart != State.REFRESHING) { - setState(State.PULL_TO_REFRESH); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - } - - private class PTROnGlobalLayoutListener implements OnGlobalLayoutListener { - - @SuppressWarnings("deprecation") - @Override - public void onGlobalLayout() { - int initialHeaderHeight = header.getHeight(); - - if (initialHeaderHeight > 0) { - measuredHeaderHeight = initialHeaderHeight; - - if (measuredHeaderHeight > 0 && state != State.REFRESHING) { - setHeaderPadding(-measuredHeaderHeight); - requestLayout(); - } - } - - getViewTreeObserver().removeGlobalOnLayoutListener(this); - } - } - - // ////////////////////////////////////////////////////////////////// - // Loading Thread & Handler */ - // ////////////////////////////////////////////////////////////////// - - private class LoadingThread extends Thread { - Handler mHandler; - - public LoadingThread(Handler mHandler) { - this.mHandler = mHandler; - } - - @Override - public void run() { - try { - while (true) { - Message msg_loading_0 = mHandler.obtainMessage(LOADINGZERO); - msg_loading_0.obj = new WeakReference( - text); - mHandler.sendMessage(msg_loading_0); - Thread.sleep(LOADINGBUFFER); - - Message msg_loading_1 = mHandler.obtainMessage(LOADINGONE); - msg_loading_1.obj = new WeakReference( - text); - mHandler.sendMessage(msg_loading_1); - Thread.sleep(LOADINGBUFFER); - - Message msg_loading_2 = mHandler.obtainMessage(LOADINGTWO); - msg_loading_2.obj = new WeakReference( - text); - mHandler.sendMessage(msg_loading_2); - Thread.sleep(LOADINGBUFFER); - - Message msg_loading_3 = mHandler - .obtainMessage(LOADINGTHREE); - msg_loading_3.obj = new WeakReference( - text); - mHandler.sendMessage(msg_loading_3); - Thread.sleep(LOADINGBUFFER); - } - } catch (InterruptedException e) { - // do nothing. - } - } - } - - private static Handler mLoadingHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - - @SuppressWarnings("unchecked") - TextView tv = ((WeakReference) msg.obj).get(); - if (tv == null) - return; - - switch (msg.what) { - case LOADINGZERO: - tv.setText("Loading"); - break; - - case LOADINGONE: - tv.setText("Loading."); - break; - - case LOADINGTWO: - tv.setText("Loading.."); - break; - - case LOADINGTHREE: - tv.setText("Loading..."); - break; - - default: - break; - } - } - }; - - // private class PTROnItemClickListener implements OnItemClickListener { - // - // @Override - // public void onItemClick(AdapterView adapterView, View view, int - // position, long id){ - // hasResetHeader = false; - // - // if(onItemClickListener != null && state == State.PULL_TO_REFRESH){ - // // Passing up onItemClick. Correct position with the number of header - // views - // onItemClickListener.onItemClick(adapterView, view, position - - // getHeaderViewsCount(), id); - // } - // } - // } - // - // private class PTROnItemLongClickListener implements - // OnItemLongClickListener{ - // - // @Override - // public boolean onItemLongClick(AdapterView adapterView, View view, int - // position, long id){ - // hasResetHeader = false; - // - // if(onItemLongClickListener != null && state == State.PULL_TO_REFRESH){ - // // Passing up onItemLongClick. Correct position with the number of header - // views - // return onItemLongClickListener.onItemLongClick(adapterView, view, - // position - getHeaderViewsCount(), id); - // } - // - // return false; - // } - // } -} diff --git a/src/main/java/com/huewu/pla/sample/PullToRefreshSampleActivity.java b/src/main/java/com/huewu/pla/sample/PullToRefreshSampleActivity.java deleted file mode 100644 index ab0669e..0000000 --- a/src/main/java/com/huewu/pla/sample/PullToRefreshSampleActivity.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.huewu.pla.sample; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.ArrayAdapter; -import android.widget.ListAdapter; - -import com.huewu.pla.R; -import com.huewu.pla.lib.internal.PLA_AdapterView; - -import java.util.Arrays; -import java.util.Random; - -public class PullToRefreshSampleActivity extends Activity { - - private class MySimpleAdapter extends ArrayAdapter { - - public MySimpleAdapter(Context context, int layoutRes) { - super(context, layoutRes, android.R.id.text1); - } - } - - private PLA_AdapterView mAdapterView = null; - private MySimpleAdapter mAdapter = null; - - @SuppressWarnings("unchecked") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.sample_pull_to_refresh_act); - //mAdapterView = (PLA_AdapterView) findViewById(R.id.list); - mAdapterView = (PLA_AdapterView) findViewById(R.id.list); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(Menu.NONE, 1001, 0, "Load More Contents"); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch(item.getItemId()){ - case 1001: - { - int startCount = mAdapter.getCount(); - for( int i = 0; i < 100; ++i){ - //generate 100 random items. - - StringBuilder builder = new StringBuilder(); - builder.append("Hello!!["); - builder.append(startCount + i); - builder.append("] "); - - char[] chars = new char[mRand.nextInt(100)]; - Arrays.fill(chars, '1'); - builder.append(chars); - mAdapter.add(builder.toString()); - } - } - break; - case 1002: - { - Intent intent = new Intent(this, PullToRefreshSampleActivity.class); - startActivity(intent); - } - break; - } - return true; - } - - @Override - protected void onResume() { - super.onResume(); - initAdapter(); - mAdapterView.setAdapter(mAdapter); - //mAdapterView.setAdapter(mAdapter); - } - - private Random mRand = new Random(); - private void initAdapter() { - mAdapter = new MySimpleAdapter(this, R.layout.sample_item); - - for( int i = 0; i < 30; ++i){ - //generate 30 random items. - - StringBuilder builder = new StringBuilder(); - builder.append("Hello!!["); - builder.append(i); - builder.append("] "); - - char[] chars = new char[mRand.nextInt(500)]; - Arrays.fill(chars, '1'); - builder.append(chars); - mAdapter.add(builder.toString()); - } - - } - -}//end of class diff --git a/src/main/java/com/huewu/pla/sample/SampleActivity.java b/src/main/java/com/huewu/pla/sample/SampleActivity.java deleted file mode 100644 index 24e3393..0000000 --- a/src/main/java/com/huewu/pla/sample/SampleActivity.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.huewu.pla.sample; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -import com.huewu.pla.R; -import com.huewu.pla.lib.MultiColumnListView; -import com.huewu.pla.lib.internal.PLA_AbsListView.LayoutParams; - -import java.util.Arrays; -import java.util.Random; - -public class SampleActivity extends Activity { - - private class MySimpleAdapter extends ArrayAdapter { - - public MySimpleAdapter(Context context, int layoutRes) { - super(context, layoutRes, android.R.id.text1); - } - } - - private MultiColumnListView mAdapterView = null; - private MySimpleAdapter mAdapter = null; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.sample_act); - //mAdapterView = (PLA_AdapterView) findViewById(R.id.list); - - mAdapterView = (MultiColumnListView) findViewById(R.id.list); - - { - for( int i = 0; i < 3; ++i ){ - //add header view. - TextView tv = new TextView(this); - tv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - tv.setText("Hello Header!! ........................................................................"); - mAdapterView.addHeaderView(tv); - } - } - { - for( int i = 0; i < 3; ++i ){ - //add footer view. - TextView tv = new TextView(this); - tv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - tv.setText("Hello Footer!! ........................................................................"); - mAdapterView.addFooterView(tv); - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(Menu.NONE, 1001, 0, "Load More Contents"); - menu.add(Menu.NONE, 1002, 0, "Launch Pull-To-Refresh Activity"); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch(item.getItemId()){ - case 1001: - { - int startCount = mAdapter.getCount(); - for( int i = 0; i < 100; ++i){ - //generate 100 random items. - - StringBuilder builder = new StringBuilder(); - builder.append("Hello!!["); - builder.append(startCount + i); - builder.append("] "); - - char[] chars = new char[mRand.nextInt(100)]; - Arrays.fill(chars, '1'); - builder.append(chars); - mAdapter.add(builder.toString()); - } - } - break; - case 1002: - { - Intent intent = new Intent(this, PullToRefreshSampleActivity.class); - startActivity(intent); - } - break; - } - return true; - } - - @Override - protected void onResume() { - super.onResume(); - initAdapter(); - mAdapterView.setAdapter(mAdapter); - //mAdapterView.setAdapter(mAdapter); - } - - private Random mRand = new Random(); - private void initAdapter() { - mAdapter = new MySimpleAdapter(this, R.layout.sample_item); - - for( int i = 0; i < 30; ++i){ - //generate 30 random items. - - StringBuilder builder = new StringBuilder(); - builder.append("Hello!!["); - builder.append(i); - builder.append("] "); - - char[] chars = new char[mRand.nextInt(500)]; - Arrays.fill(chars, '1'); - builder.append(chars); - mAdapter.add(builder.toString()); - } - - } - -}//end of class