pages = new ArrayList<>();
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_main);
25 |
26 | cycleViewPager = (CycleViewPager)findViewById(R.id.pp);
27 | pages.add(LayoutInflater.from(this).inflate(R.layout.page1, null));
28 | pages.add(LayoutInflater.from(this).inflate(R.layout.page2, null));
29 | pages.add(LayoutInflater.from(this).inflate(R.layout.page3, null));
30 |
31 | cycleViewPager.setAdapter(new PagerAdapter() {
32 | @Override
33 | public int getCount() {
34 | return pages.size();
35 | }
36 |
37 | @Override
38 | public boolean isViewFromObject(View view, Object object) {
39 | return view == object;
40 | }
41 |
42 | @Override
43 | public void destroyItem(ViewGroup container, int position,
44 | Object object) {
45 | //Toast.makeText(MainActivity.this, "destroyItem:"+position, Toast.LENGTH_SHORT).show();
46 | ((CycleViewPager) container).removeView(pages.get(position));
47 | }
48 |
49 | @Override
50 | public Object instantiateItem(ViewGroup container, int position) {
51 | ((CycleViewPager) container).addView(pages.get(position));
52 | return pages.get(position);
53 | }
54 | });
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/page1.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/page2.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/page3.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaozhentao/CycleViewPager/73d6f0c50cb88a8adda15dabd7f3a6c2398942cc/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaozhentao/CycleViewPager/73d6f0c50cb88a8adda15dabd7f3a6c2398942cc/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaozhentao/CycleViewPager/73d6f0c50cb88a8adda15dabd7f3a6c2398942cc/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/pic1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaozhentao/CycleViewPager/73d6f0c50cb88a8adda15dabd7f3a6c2398942cc/app/src/main/res/mipmap-xhdpi/pic1.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/pic2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaozhentao/CycleViewPager/73d6f0c50cb88a8adda15dabd7f3a6c2398942cc/app/src/main/res/mipmap-xhdpi/pic2.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/pic3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaozhentao/CycleViewPager/73d6f0c50cb88a8adda15dabd7f3a6c2398942cc/app/src/main/res/mipmap-xhdpi/pic3.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaozhentao/CycleViewPager/73d6f0c50cb88a8adda15dabd7f3a6c2398942cc/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CycleViewPager
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.1.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaozhentao/CycleViewPager/73d6f0c50cb88a8adda15dabd7f3a6c2398942cc/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
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 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | minSdkVersion 11
9 | targetSdkVersion 22
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | compile 'com.android.support:appcompat-v7:22.1.1'
24 | }
25 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in H:/AndroidDev/androidsdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/library/src/main/java/com/zzt/library/CycleViewPager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.zzt.library;
18 |
19 | import android.content.Context;
20 | import android.content.res.Resources;
21 | import android.content.res.TypedArray;
22 | import android.database.DataSetObserver;
23 | import android.graphics.Canvas;
24 | import android.graphics.Rect;
25 | import android.graphics.drawable.Drawable;
26 | import android.os.Build;
27 | import android.os.Bundle;
28 | import android.os.Parcel;
29 | import android.os.Parcelable;
30 | import android.os.SystemClock;
31 | import android.support.annotation.DrawableRes;
32 | import android.support.v4.os.ParcelableCompat;
33 | import android.support.v4.os.ParcelableCompatCreatorCallbacks;
34 | import android.support.v4.view.AccessibilityDelegateCompat;
35 | import android.support.v4.view.KeyEventCompat;
36 | import android.support.v4.view.MotionEventCompat;
37 | import android.support.v4.view.PagerAdapter;
38 | import android.support.v4.view.VelocityTrackerCompat;
39 | import android.support.v4.view.ViewCompat;
40 | import android.support.v4.view.ViewConfigurationCompat;
41 | import android.support.v4.view.accessibility.AccessibilityEventCompat;
42 | import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
43 | import android.support.v4.view.accessibility.AccessibilityRecordCompat;
44 | import android.support.v4.widget.EdgeEffectCompat;
45 | import android.util.AttributeSet;
46 | import android.util.Log;
47 | import android.view.FocusFinder;
48 | import android.view.Gravity;
49 | import android.view.KeyEvent;
50 | import android.view.MotionEvent;
51 | import android.view.SoundEffectConstants;
52 | import android.view.VelocityTracker;
53 | import android.view.View;
54 | import android.view.ViewConfiguration;
55 | import android.view.ViewGroup;
56 | import android.view.ViewParent;
57 | import android.view.accessibility.AccessibilityEvent;
58 | import android.view.animation.Interpolator;
59 | import android.widget.Scroller;
60 |
61 | import java.lang.reflect.Method;
62 | import java.util.ArrayList;
63 | import java.util.Collections;
64 | import java.util.Comparator;
65 |
66 | /**
67 | * Layout manager that allows the user to flip left and right
68 | * through pages of data. You supply an implementation of a
69 | * {@link PagerAdapter} to generate the pages that the view shows.
70 | *
71 | * Note this class is currently under early design and
72 | * development. The API will likely change in later updates of
73 | * the compatibility library, requiring changes to the source code
74 | * of apps when they are compiled against the newer version.
75 | *
76 | * ViewPager is most often used in conjunction with {@link android.app.Fragment},
77 | * which is a convenient way to supply and manage the lifecycle of each page.
78 | * There are standard adapters implemented for using fragments with the ViewPager,
79 | * which cover the most common use cases. These are
80 | * {@link android.support.v4.app.FragmentPagerAdapter} and
81 | * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these
82 | * classes have simple code showing how to build a full user interface
83 | * with them.
84 | *
85 | *
For more information about how to use ViewPager, read Creating Swipe Views with
87 | * Tabs.
88 | *
89 | * Below is a more complicated example of ViewPager, using it in conjunction
90 | * with {@link android.app.ActionBar} tabs. You can find other examples of using
91 | * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
92 | *
93 | * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
94 | * complete}
95 | */
96 | public class CycleViewPager extends ViewGroup {
97 | private static final String TAG = "ViewPager";
98 | private static final boolean DEBUG = false;
99 |
100 | private static final boolean USE_CACHE = false;
101 |
102 | private static final int DEFAULT_OFFSCREEN_PAGES = 1;
103 | private static final int MAX_SETTLE_DURATION = 600; // ms
104 | private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
105 |
106 | private static final int DEFAULT_GUTTER_SIZE = 16; // dips
107 |
108 | private static final int MIN_FLING_VELOCITY = 400; // dips
109 |
110 | private static final int[] LAYOUT_ATTRS = new int[] {
111 | android.R.attr.layout_gravity
112 | };
113 |
114 | /**
115 | * Used to track what the expected number of items in the adapter should be.
116 | * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
117 | */
118 | private int mExpectedAdapterCount;
119 |
120 | static class ItemInfo {
121 | Object object;
122 | int position;
123 | boolean scrolling;
124 | float widthFactor;
125 | float offset;
126 | }
127 |
128 | private static final Comparator COMPARATOR = new Comparator(){
129 | @Override
130 | public int compare(ItemInfo lhs, ItemInfo rhs) {
131 | return lhs.position - rhs.position;
132 | }
133 | };
134 |
135 | private static final Interpolator sInterpolator = new Interpolator() {
136 | public float getInterpolation(float t) {
137 | t -= 1.0f;
138 | return t * t * t * t * t + 1.0f;
139 | }
140 | };
141 |
142 | private final ArrayList mItems = new ArrayList();
143 | private final ItemInfo mTempItem = new ItemInfo();
144 |
145 | private final Rect mTempRect = new Rect();
146 |
147 | private PagerAdapter mAdapter;
148 | private int mCurItem; // Index of currently displayed page.
149 | private int mRestoredCurItem = -1;
150 | private Parcelable mRestoredAdapterState = null;
151 | private ClassLoader mRestoredClassLoader = null;
152 | private Scroller mScroller;
153 | private PagerObserver mObserver;
154 |
155 | private int mPageMargin;
156 | private Drawable mMarginDrawable;
157 | private int mTopPageBounds;
158 | private int mBottomPageBounds;
159 |
160 | // Offsets of the first and last items, if known.
161 | // Set during population, used to determine if we are at the beginning
162 | // or end of the pager data set during touch scrolling.
163 | private float mFirstOffset = -Float.MAX_VALUE;
164 | private float mLastOffset = Float.MAX_VALUE;
165 |
166 | private int mChildWidthMeasureSpec;
167 | private int mChildHeightMeasureSpec;
168 | private boolean mInLayout;
169 |
170 | private boolean mScrollingCacheEnabled;
171 |
172 | private boolean mPopulatePending;
173 | private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
174 |
175 | private boolean mIsBeingDragged;
176 | private boolean mIsUnableToDrag;
177 | private int mDefaultGutterSize;
178 | private int mGutterSize;
179 | private int mTouchSlop;
180 | /**
181 | * Position of the last motion event.
182 | */
183 | private float mLastMotionX;
184 | private float mLastMotionY;
185 | private float mInitialMotionX;
186 | private float mInitialMotionY;
187 | /**
188 | * ID of the active pointer. This is used to retain consistency during
189 | * drags/flings if multiple pointers are used.
190 | */
191 | private int mActivePointerId = INVALID_POINTER;
192 | /**
193 | * Sentinel value for no current active pointer.
194 | * Used by {@link #mActivePointerId}.
195 | */
196 | private static final int INVALID_POINTER = -1;
197 |
198 | /**
199 | * Determines speed during touch scrolling
200 | */
201 | private VelocityTracker mVelocityTracker;
202 | private int mMinimumVelocity;
203 | private int mMaximumVelocity;
204 | private int mFlingDistance;
205 | private int mCloseEnough;
206 |
207 | // If the pager is at least this close to its final position, complete the scroll
208 | // on touch down and let the user interact with the content inside instead of
209 | // "catching" the flinging pager.
210 | private static final int CLOSE_ENOUGH = 2; // dp
211 |
212 | private boolean mFakeDragging;
213 | private long mFakeDragBeginTime;
214 |
215 | private EdgeEffectCompat mLeftEdge;
216 | private EdgeEffectCompat mRightEdge;
217 |
218 | private boolean mFirstLayout = true;
219 | private boolean mNeedCalculatePageOffsets = false;
220 | private boolean mCalledSuper;
221 | private int mDecorChildCount;
222 |
223 | private OnPageChangeListener mOnPageChangeListener;
224 | private OnPageChangeListener mInternalPageChangeListener;
225 | private OnAdapterChangeListener mAdapterChangeListener;
226 | private PageTransformer mPageTransformer;
227 | private Method mSetChildrenDrawingOrderEnabled;
228 |
229 | private static final int DRAW_ORDER_DEFAULT = 0;
230 | private static final int DRAW_ORDER_FORWARD = 1;
231 | private static final int DRAW_ORDER_REVERSE = 2;
232 | private int mDrawingOrder;
233 | private ArrayList mDrawingOrderedChildren;
234 | private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
235 |
236 | /**
237 | * Indicates that the pager is in an idle, settled state. The current page
238 | * is fully in view and no animation is in progress.
239 | */
240 | public static final int SCROLL_STATE_IDLE = 0;
241 |
242 | /**
243 | * Indicates that the pager is currently being dragged by the user.
244 | */
245 | public static final int SCROLL_STATE_DRAGGING = 1;
246 |
247 | /**
248 | * Indicates that the pager is in the process of settling to a final position.
249 | */
250 | public static final int SCROLL_STATE_SETTLING = 2;
251 |
252 | private final Runnable mEndScrollRunnable = new Runnable() {
253 | public void run() {
254 | setScrollState(SCROLL_STATE_IDLE);
255 | populate();
256 | }
257 | };
258 |
259 | private int mScrollState = SCROLL_STATE_IDLE;
260 |
261 | /**
262 | * Callback interface for responding to changing state of the selected page.
263 | */
264 | public interface OnPageChangeListener {
265 |
266 | /**
267 | * This method will be invoked when the current page is scrolled, either as part
268 | * of a programmatically initiated smooth scroll or a user initiated touch scroll.
269 | *
270 | * @param position Position index of the first page currently being displayed.
271 | * Page position+1 will be visible if positionOffset is nonzero.
272 | * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
273 | * @param positionOffsetPixels Value in pixels indicating the offset from position.
274 | */
275 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
276 |
277 | /**
278 | * This method will be invoked when a new page becomes selected. Animation is not
279 | * necessarily complete.
280 | *
281 | * @param position Position index of the new selected page.
282 | */
283 | public void onPageSelected(int position);
284 |
285 | /**
286 | * Called when the scroll state changes. Useful for discovering when the user
287 | * begins dragging, when the pager is automatically settling to the current page,
288 | * or when it is fully stopped/idle.
289 | *
290 | * @param state The new scroll state.
291 | * @see CycleViewPager#SCROLL_STATE_IDLE
292 | * @see CycleViewPager#SCROLL_STATE_DRAGGING
293 | * @see CycleViewPager#SCROLL_STATE_SETTLING
294 | */
295 | public void onPageScrollStateChanged(int state);
296 | }
297 |
298 | /**
299 | * Simple implementation of the {@link OnPageChangeListener} interface with stub
300 | * implementations of each method. Extend this if you do not intend to override
301 | * every method of {@link OnPageChangeListener}.
302 | */
303 | public static class SimpleOnPageChangeListener implements OnPageChangeListener {
304 | @Override
305 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
306 | // This space for rent
307 | }
308 |
309 | @Override
310 | public void onPageSelected(int position) {
311 | // This space for rent
312 | }
313 |
314 | @Override
315 | public void onPageScrollStateChanged(int state) {
316 | // This space for rent
317 | }
318 | }
319 |
320 | /**
321 | * A PageTransformer is invoked whenever a visible/attached page is scrolled.
322 | * This offers an opportunity for the application to apply a custom transformation
323 | * to the page views using animation properties.
324 | *
325 | * As property animation is only supported as of Android 3.0 and forward,
326 | * setting a PageTransformer on a ViewPager on earlier platform versions will
327 | * be ignored.
328 | */
329 | public interface PageTransformer {
330 | /**
331 | * Apply a property transformation to the given page.
332 | *
333 | * @param page Apply the transformation to this page
334 | * @param position Position of page relative to the current front-and-center
335 | * position of the pager. 0 is front and center. 1 is one full
336 | * page position to the right, and -1 is one page position to the left.
337 | */
338 | public void transformPage(View page, float position);
339 | }
340 |
341 | /**
342 | * Used internally to monitor when adapters are switched.
343 | */
344 | interface OnAdapterChangeListener {
345 | public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
346 | }
347 |
348 | /**
349 | * Used internally to tag special types of child views that should be added as
350 | * pager decorations by default.
351 | */
352 | interface Decor {}
353 |
354 | public CycleViewPager(Context context) {
355 | super(context);
356 | initViewPager();
357 | }
358 |
359 | public CycleViewPager(Context context, AttributeSet attrs) {
360 | super(context, attrs);
361 | initViewPager();
362 | }
363 |
364 | void initViewPager() {
365 | setWillNotDraw(false);
366 | setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
367 | setFocusable(true);
368 | final Context context = getContext();
369 | mScroller = new Scroller(context, sInterpolator);
370 | final ViewConfiguration configuration = ViewConfiguration.get(context);
371 | final float density = context.getResources().getDisplayMetrics().density;
372 |
373 | mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
374 | mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
375 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
376 | mLeftEdge = new EdgeEffectCompat(context);
377 | mRightEdge = new EdgeEffectCompat(context);
378 |
379 | mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
380 | mCloseEnough = (int) (CLOSE_ENOUGH * density);
381 | mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
382 |
383 | ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
384 |
385 | if (ViewCompat.getImportantForAccessibility(this)
386 | == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
387 | ViewCompat.setImportantForAccessibility(this,
388 | ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
389 | }
390 | }
391 |
392 | @Override
393 | protected void onDetachedFromWindow() {
394 | removeCallbacks(mEndScrollRunnable);
395 | super.onDetachedFromWindow();
396 | }
397 |
398 | private void setScrollState(int newState) {
399 | if (mScrollState == newState) {
400 | return;
401 | }
402 |
403 | mScrollState = newState;
404 | if (mPageTransformer != null) {
405 | // PageTransformers can do complex things that benefit from hardware layers.
406 | enableLayers(newState != SCROLL_STATE_IDLE);
407 | }
408 | if (mOnPageChangeListener != null) {
409 | mOnPageChangeListener.onPageScrollStateChanged(newState);
410 | }
411 | }
412 |
413 | /**
414 | * Set a PagerAdapter that will supply views for this pager as needed.
415 | *
416 | * @param adapter Adapter to use
417 | */
418 | public void setAdapter(PagerAdapter adapter) {
419 | if (mAdapter != null) {
420 | mAdapter.unregisterDataSetObserver(mObserver);
421 | mAdapter.startUpdate(this);
422 | for (int i = 0; i < mItems.size(); i++) {
423 | final ItemInfo ii = mItems.get(i);
424 | mAdapter.destroyItem(this, ii.position, ii.object);
425 | }
426 | mAdapter.finishUpdate(this);
427 | mItems.clear();
428 | removeNonDecorViews();
429 | mCurItem = 0;
430 | scrollTo(0, 0);
431 | }
432 |
433 | final PagerAdapter oldAdapter = mAdapter;
434 | mAdapter = adapter;
435 | mExpectedAdapterCount = 0;
436 |
437 | if (mAdapter != null) {
438 | if (mObserver == null) {
439 | mObserver = new PagerObserver();
440 | }
441 | mAdapter.registerDataSetObserver(mObserver);
442 | mPopulatePending = false;
443 | final boolean wasFirstLayout = mFirstLayout;
444 | mFirstLayout = true;
445 | mExpectedAdapterCount = mAdapter.getCount();
446 | if (mRestoredCurItem >= 0) {
447 | mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
448 | setCurrentItemInternal(mRestoredCurItem, false, true);
449 | mRestoredCurItem = -1;
450 | mRestoredAdapterState = null;
451 | mRestoredClassLoader = null;
452 | } else if (!wasFirstLayout) {
453 | populate();
454 | } else {
455 | requestLayout();
456 | }
457 | }
458 |
459 | if (mAdapterChangeListener != null && oldAdapter != adapter) {
460 | mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
461 | }
462 | }
463 |
464 | private void removeNonDecorViews() {
465 | for (int i = 0; i < getChildCount(); i++) {
466 | final View child = getChildAt(i);
467 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
468 | if (!lp.isDecor) {
469 | removeViewAt(i);
470 | i--;
471 | }
472 | }
473 | }
474 |
475 | /**
476 | * Retrieve the current adapter supplying pages.
477 | *
478 | * @return The currently registered PagerAdapter
479 | */
480 | public PagerAdapter getAdapter() {
481 | return mAdapter;
482 | }
483 |
484 | void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
485 | mAdapterChangeListener = listener;
486 | }
487 |
488 | private int getClientWidth() {
489 | return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
490 | }
491 |
492 | /**
493 | * Set the currently selected page. If the ViewPager has already been through its first
494 | * layout with its current adapter there will be a smooth animated transition between
495 | * the current item and the specified item.
496 | *
497 | * @param item Item index to select
498 | */
499 | public void setCurrentItem(int item) {
500 | mPopulatePending = false;
501 | setCurrentItemInternal(item, !mFirstLayout, false);
502 | }
503 |
504 | /**
505 | * Set the currently selected page.
506 | *
507 | * @param item Item index to select
508 | * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
509 | */
510 | public void setCurrentItem(int item, boolean smoothScroll) {
511 | mPopulatePending = false;
512 | setCurrentItemInternal(item, smoothScroll, false);
513 | }
514 |
515 | public int getCurrentItem() {
516 | return mCurItem;
517 | }
518 |
519 | void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
520 | setCurrentItemInternal(item, smoothScroll, always, 0);
521 | }
522 |
523 | void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
524 | if (mAdapter == null || mAdapter.getCount() <= 0) {
525 | setScrollingCacheEnabled(false);
526 | return;
527 | }
528 | if (!always && mCurItem == item && mItems.size() != 0) {
529 | setScrollingCacheEnabled(false);
530 | return;
531 | }
532 |
533 | final int pageLimit = mOffscreenPageLimit;
534 | if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
535 | // We are doing a jump by more than one page. To avoid
536 | // glitches, we want to keep all current pages in the view
537 | // until the scroll ends.
538 | for (int i=0; i= 0) {
573 | newItem = item % mAdapter.getCount();
574 | }else{
575 | if((item % mAdapter.getCount()) != 0)
576 | newItem = mAdapter.getCount() - (-item)%mAdapter.getCount();
577 | else
578 | newItem = 0;
579 | }
580 |
581 | if (smoothScroll) {
582 | smoothScrollTo(destX, 0, velocity);
583 | if (dispatchSelected && mOnPageChangeListener != null) {
584 | mOnPageChangeListener.onPageSelected(newItem);
585 | }
586 | if (dispatchSelected && mInternalPageChangeListener != null) {
587 | mInternalPageChangeListener.onPageSelected(newItem);
588 | }
589 | } else {
590 | if (dispatchSelected && mOnPageChangeListener != null) {
591 | mOnPageChangeListener.onPageSelected(newItem);
592 | }
593 | if (dispatchSelected && mInternalPageChangeListener != null) {
594 | mInternalPageChangeListener.onPageSelected(newItem);
595 | }
596 | completeScroll(false);
597 | scrollTo(destX, 0);
598 | pageScrolled(destX);
599 | }
600 | }
601 |
602 | /**
603 | * Set a listener that will be invoked whenever the page changes or is incrementally
604 | * scrolled. See {@link OnPageChangeListener}.
605 | *
606 | * @param listener Listener to set
607 | */
608 | public void setOnPageChangeListener(OnPageChangeListener listener) {
609 | mOnPageChangeListener = listener;
610 | }
611 |
612 | /**
613 | * Set a {@link PageTransformer} that will be called for each attached page whenever
614 | * the scroll position is changed. This allows the application to apply custom property
615 | * transformations to each page, overriding the default sliding look and feel.
616 | *
617 | * Note: Prior to Android 3.0 the property animation APIs did not exist.
618 | * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.
619 | *
620 | * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
621 | * to be drawn from last to first instead of first to last.
622 | * @param transformer PageTransformer that will modify each page's animation properties
623 | */
624 | public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
625 | if (Build.VERSION.SDK_INT >= 11) {
626 | final boolean hasTransformer = transformer != null;
627 | final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
628 | mPageTransformer = transformer;
629 | setChildrenDrawingOrderEnabledCompat(hasTransformer);
630 | if (hasTransformer) {
631 | mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
632 | } else {
633 | mDrawingOrder = DRAW_ORDER_DEFAULT;
634 | }
635 | if (needsPopulate) populate();
636 | }
637 | }
638 |
639 | void setChildrenDrawingOrderEnabledCompat(boolean enable) {
640 | if (Build.VERSION.SDK_INT >= 7) {
641 | if (mSetChildrenDrawingOrderEnabled == null) {
642 | try {
643 | mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(
644 | "setChildrenDrawingOrderEnabled", new Class[] { Boolean.TYPE });
645 | } catch (NoSuchMethodException e) {
646 | Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
647 | }
648 | }
649 | try {
650 | mSetChildrenDrawingOrderEnabled.invoke(this, enable);
651 | } catch (Exception e) {
652 | Log.e(TAG, "Error changing children drawing order", e);
653 | }
654 | }
655 | }
656 |
657 | @Override
658 | protected int getChildDrawingOrder(int childCount, int i) {
659 | final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
660 | final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
661 | return result;
662 | }
663 |
664 | /**
665 | * Set a separate OnPageChangeListener for internal use by the support library.
666 | *
667 | * @param listener Listener to set
668 | * @return The old listener that was set, if any.
669 | */
670 | OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
671 | OnPageChangeListener oldListener = mInternalPageChangeListener;
672 | mInternalPageChangeListener = listener;
673 | return oldListener;
674 | }
675 |
676 | /**
677 | * Returns the number of pages that will be retained to either side of the
678 | * current page in the view hierarchy in an idle state. Defaults to 1.
679 | *
680 | * @return How many pages will be kept offscreen on either side
681 | * @see #setOffscreenPageLimit(int)
682 | */
683 | public int getOffscreenPageLimit() {
684 | return mOffscreenPageLimit;
685 | }
686 |
687 | /**
688 | * Set the number of pages that should be retained to either side of the
689 | * current page in the view hierarchy in an idle state. Pages beyond this
690 | * limit will be recreated from the adapter when needed.
691 | *
692 | * This is offered as an optimization. If you know in advance the number
693 | * of pages you will need to support or have lazy-loading mechanisms in place
694 | * on your pages, tweaking this setting can have benefits in perceived smoothness
695 | * of paging animations and interaction. If you have a small number of pages (3-4)
696 | * that you can keep active all at once, less time will be spent in layout for
697 | * newly created view subtrees as the user pages back and forth.
698 | *
699 | * You should keep this limit low, especially if your pages have complex layouts.
700 | * This setting defaults to 1.
701 | *
702 | * @param limit How many pages will be kept offscreen in an idle state.
703 | */
704 | public void setOffscreenPageLimit(int limit) {
705 | if (limit < DEFAULT_OFFSCREEN_PAGES) {
706 | Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
707 | DEFAULT_OFFSCREEN_PAGES);
708 | limit = DEFAULT_OFFSCREEN_PAGES;
709 | }
710 | if (limit != mOffscreenPageLimit) {
711 | mOffscreenPageLimit = limit;
712 | populate();
713 | }
714 | }
715 |
716 | /**
717 | * Set the margin between pages.
718 | *
719 | * @param marginPixels Distance between adjacent pages in pixels
720 | * @see #getPageMargin()
721 | * @see #setPageMarginDrawable(android.graphics.drawable.Drawable)
722 | * @see #setPageMarginDrawable(int)
723 | */
724 | public void setPageMargin(int marginPixels) {
725 | final int oldMargin = mPageMargin;
726 | mPageMargin = marginPixels;
727 |
728 | final int width = getWidth();
729 | recomputeScrollPosition(width, width, marginPixels, oldMargin);
730 |
731 | requestLayout();
732 | }
733 |
734 | /**
735 | * Return the margin between pages.
736 | *
737 | * @return The size of the margin in pixels
738 | */
739 | public int getPageMargin() {
740 | return mPageMargin;
741 | }
742 |
743 | /**
744 | * Set a drawable that will be used to fill the margin between pages.
745 | *
746 | * @param d Drawable to display between pages
747 | */
748 | public void setPageMarginDrawable(Drawable d) {
749 | mMarginDrawable = d;
750 | if (d != null) refreshDrawableState();
751 | setWillNotDraw(d == null);
752 | invalidate();
753 | }
754 |
755 | /**
756 | * Set a drawable that will be used to fill the margin between pages.
757 | *
758 | * @param resId Resource ID of a drawable to display between pages
759 | */
760 | public void setPageMarginDrawable(@DrawableRes int resId) {
761 | setPageMarginDrawable(getContext().getResources().getDrawable(resId));
762 | }
763 |
764 | @Override
765 | protected boolean verifyDrawable(Drawable who) {
766 | return super.verifyDrawable(who) || who == mMarginDrawable;
767 | }
768 |
769 | @Override
770 | protected void drawableStateChanged() {
771 | super.drawableStateChanged();
772 | final Drawable d = mMarginDrawable;
773 | if (d != null && d.isStateful()) {
774 | d.setState(getDrawableState());
775 | }
776 | }
777 |
778 | // We want the duration of the page snap animation to be influenced by the distance that
779 | // the screen has to travel, however, we don't want this duration to be effected in a
780 | // purely linear fashion. Instead, we use this method to moderate the effect that the distance
781 | // of travel has on the overall snap duration.
782 | float distanceInfluenceForSnapDuration(float f) {
783 | f -= 0.5f; // center the values about 0.
784 | f *= 0.3f * Math.PI / 2.0f;
785 | return (float) Math.sin(f);
786 | }
787 |
788 | /**
789 | * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
790 | *
791 | * @param x the number of pixels to scroll by on the X axis
792 | * @param y the number of pixels to scroll by on the Y axis
793 | */
794 | void smoothScrollTo(int x, int y) {
795 | smoothScrollTo(x, y, 0);
796 | }
797 |
798 | /**
799 | * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
800 | *
801 | * @param x the number of pixels to scroll by on the X axis
802 | * @param y the number of pixels to scroll by on the Y axis
803 | * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
804 | */
805 | void smoothScrollTo(int x, int y, int velocity) {
806 | if (getChildCount() == 0) {
807 | // Nothing to do.
808 | setScrollingCacheEnabled(false);
809 | return;
810 | }
811 | int sx = getScrollX();
812 | int sy = getScrollY();
813 | int dx = x - sx;
814 | int dy = y - sy;
815 | if (dx == 0 && dy == 0) {
816 | completeScroll(false);
817 | populate();
818 | setScrollState(SCROLL_STATE_IDLE);
819 | return;
820 | }
821 |
822 | setScrollingCacheEnabled(true);
823 | setScrollState(SCROLL_STATE_SETTLING);
824 |
825 | final int width = getClientWidth();
826 | final int halfWidth = width / 2;
827 | final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
828 | final float distance = halfWidth + halfWidth *
829 | distanceInfluenceForSnapDuration(distanceRatio);
830 |
831 | int duration = 0;
832 | velocity = Math.abs(velocity);
833 | if (velocity > 0) {
834 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
835 | } else {
836 | final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
837 | final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
838 | duration = (int) ((pageDelta + 1) * 100);
839 | }
840 | duration = Math.min(duration, MAX_SETTLE_DURATION);
841 |
842 | mScroller.startScroll(sx, sy, dx, dy, duration);
843 | ViewCompat.postInvalidateOnAnimation(this);
844 | }
845 |
846 | ItemInfo addNewItem(int position, int index) {
847 | ItemInfo ii = new ItemInfo();
848 | ii.position = position;
849 | if(position >=0)
850 | ii.object = mAdapter.instantiateItem(this, position % mAdapter.getCount());
851 | else {
852 | int pos;
853 | if((position % mAdapter.getCount()) != 0)
854 | pos = mAdapter.getCount() - (-position)%mAdapter.getCount();
855 | else
856 | pos = 0;
857 | ii.object = mAdapter.instantiateItem(this, pos);
858 | }
859 |
860 | ii.widthFactor = mAdapter.getPageWidth(position);
861 | if (index < 0 || index >= mItems.size()) {
862 | mItems.add(ii);
863 | } else {
864 | mItems.add(index, ii);
865 | }
866 | return ii;
867 | }
868 |
869 | void dataSetChanged() {
870 | // This method only gets called if our observer is attached, so mAdapter is non-null.
871 |
872 | final int adapterCount = mAdapter.getCount();
873 | mExpectedAdapterCount = adapterCount;
874 | boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
875 | mItems.size() < adapterCount;
876 | int newCurrItem = mCurItem;
877 |
878 | boolean isUpdating = false;
879 | for (int i = 0; i < mItems.size(); i++) {
880 | final ItemInfo ii = mItems.get(i);
881 | final int newPos = mAdapter.getItemPosition(ii.object);
882 |
883 | if (newPos == PagerAdapter.POSITION_UNCHANGED) {
884 | continue;
885 | }
886 |
887 | if (newPos == PagerAdapter.POSITION_NONE) {
888 | mItems.remove(i);
889 | i--;
890 |
891 | if (!isUpdating) {
892 | mAdapter.startUpdate(this);
893 | isUpdating = true;
894 | }
895 |
896 | mAdapter.destroyItem(this, ii.position, ii.object);
897 | needPopulate = true;
898 |
899 | if (mCurItem == ii.position) {
900 | // Keep the current item in the valid range
901 | newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
902 | needPopulate = true;
903 | }
904 | continue;
905 | }
906 |
907 | if (ii.position != newPos) {
908 | if (ii.position == mCurItem) {
909 | // Our current item changed position. Follow it.
910 | newCurrItem = newPos;
911 | }
912 |
913 | ii.position = newPos;
914 | needPopulate = true;
915 | }
916 | }
917 |
918 | if (isUpdating) {
919 | mAdapter.finishUpdate(this);
920 | }
921 |
922 | Collections.sort(mItems, COMPARATOR);
923 |
924 | if (needPopulate) {
925 | // Reset our known page widths; populate will recompute them.
926 | final int childCount = getChildCount();
927 | for (int i = 0; i < childCount; i++) {
928 | final View child = getChildAt(i);
929 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
930 | if (!lp.isDecor) {
931 | lp.widthFactor = 0.f;
932 | }
933 | }
934 |
935 | setCurrentItemInternal(newCurrItem, false, true);
936 | requestLayout();
937 | }
938 | }
939 |
940 | void populate() {
941 | populate(mCurItem);
942 | }
943 |
944 | void populate(int newCurrentItem) {
945 | ItemInfo oldCurInfo = null;
946 | int focusDirection = View.FOCUS_FORWARD;
947 | if (mCurItem != newCurrentItem) {
948 | focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
949 | oldCurInfo = infoForPosition(mCurItem);
950 | mCurItem = newCurrentItem;
951 | }
952 |
953 | if (mAdapter == null) {
954 | sortChildDrawingOrder();
955 | return;
956 | }
957 |
958 | // Bail now if we are waiting to populate. This is to hold off
959 | // on creating views from the time the user releases their finger to
960 | // fling to a new position until we have finished the scroll to
961 | // that position, avoiding glitches from happening at that point.
962 | if (mPopulatePending) {
963 | if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
964 | sortChildDrawingOrder();
965 | return;
966 | }
967 |
968 | // Also, don't populate until we are attached to a window. This is to
969 | // avoid trying to populate before we have restored our view hierarchy
970 | // state and conflicting with what is restored.
971 | if (getWindowToken() == null) {
972 | return;
973 | }
974 |
975 | mAdapter.startUpdate(this);
976 |
977 | final int pageLimit = mOffscreenPageLimit;
978 | final int startPos = Math.max(0, mCurItem - pageLimit);
979 | final int N = mAdapter.getCount();
980 | final int endPos = Math.min(N-1, mCurItem + pageLimit);
981 |
982 | if (N != mExpectedAdapterCount) {
983 | String resName;
984 | try {
985 | resName = getResources().getResourceName(getId());
986 | } catch (Resources.NotFoundException e) {
987 | resName = Integer.toHexString(getId());
988 | }
989 | throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
990 | " contents without calling PagerAdapter#notifyDataSetChanged!" +
991 | " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
992 | " Pager id: " + resName +
993 | " Pager class: " + getClass() +
994 | " Problematic adapter: " + mAdapter.getClass());
995 | }
996 |
997 | // Locate the currently focused item or add it if needed.
998 | int curIndex = -1;
999 | ItemInfo curItem = null;
1000 | for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
1001 | final ItemInfo ii = mItems.get(curIndex);
1002 | if (ii.position >= mCurItem) {
1003 | if (ii.position == mCurItem) curItem = ii;
1004 | break;
1005 | }
1006 | }
1007 |
1008 | if (curItem == null && N > 0) {
1009 | curItem = addNewItem(mCurItem, curIndex);
1010 | }
1011 |
1012 | // Fill 3x the available width or up to the number of offscreen
1013 | // pages requested to either side, whichever is larger.
1014 | // If we have no current item we have no work to do.
1015 | if (curItem != null) {
1016 | float extraWidthLeft = 0.f;
1017 | int itemIndex = curIndex - 1;
1018 | ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
1019 | final int clientWidth = getClientWidth();
1020 | final float leftWidthNeeded = clientWidth <= 0 ? 0 :
1021 | 2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
1022 |
1023 | final int nl = mCurItem - 2;
1024 | for (int pos = mCurItem - 1; pos >= 0 || pos >= nl; pos--) {
1025 | if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
1026 | if (ii == null) {
1027 | break;
1028 | }
1029 | if (pos == ii.position && !ii.scrolling) {
1030 | mItems.remove(itemIndex);
1031 |
1032 | if(pos >= 0) {
1033 | mAdapter.destroyItem(this, pos % N, ii.object);
1034 | }else{
1035 | int pPos;
1036 | if((pos % mAdapter.getCount()) != 0)
1037 | pPos = mAdapter.getCount() - (-pos)%mAdapter.getCount();
1038 | else
1039 | pPos = 0;
1040 | mAdapter.destroyItem(this, pPos, ii.object);
1041 | }
1042 |
1043 | if (DEBUG) {
1044 | Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
1045 | " view: " + ((View) ii.object));
1046 | }
1047 | itemIndex--;
1048 | curIndex--;
1049 | ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
1050 | }
1051 | } else if (ii != null && pos == ii.position) {
1052 | extraWidthLeft += ii.widthFactor;
1053 | itemIndex--;
1054 | ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
1055 | if(itemIndex < 0){
1056 | int id;
1057 | if((itemIndex % mItems.size()) != 0)
1058 | id = mItems.size() - (-itemIndex)%mItems.size();
1059 | else
1060 | id = 0;
1061 | ii = mItems.get(id);
1062 | }
1063 | } else {
1064 | //检查要准备加载出来的这一项是否已经被加载,如果是则要先销毁
1065 | int tempPos;
1066 | if(pos >= 0) {
1067 | tempPos = pos % N;
1068 | }else{
1069 | if((pos % mAdapter.getCount()) != 0)
1070 | tempPos = mAdapter.getCount() - (-pos)%mAdapter.getCount();
1071 | else
1072 | tempPos = 0;
1073 | }
1074 | for(int j=0; j= 0) {
1078 | tempItemPosition = itemPosition % N;
1079 | }else{
1080 | if((itemPosition % mAdapter.getCount()) != 0)
1081 | tempItemPosition = mAdapter.getCount() - (-itemPosition)%mAdapter.getCount();
1082 | else
1083 | tempItemPosition = 0;
1084 | }
1085 | if(tempPos == tempItemPosition){
1086 | mAdapter.destroyItem(this, tempPos, mItems.get(j));
1087 | mItems.remove(j);
1088 | }
1089 | }
1090 | ii = addNewItem(pos, itemIndex + 1);
1091 | extraWidthLeft += ii.widthFactor;
1092 | curIndex++;
1093 | ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
1094 | }
1095 | }
1096 |
1097 | float extraWidthRight = curItem.widthFactor;
1098 | itemIndex = curIndex + 1;
1099 | if (extraWidthRight < 2.f) {
1100 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
1101 | final float rightWidthNeeded = clientWidth <= 0 ? 0 :
1102 | (float) getPaddingRight() / (float) clientWidth + 2.f;
1103 | final int n = mCurItem + 2;
1104 | for (int pos = mCurItem + 1; pos < N || pos <= n; pos++) {
1105 | if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
1106 | if (ii == null) {
1107 | break;
1108 | }
1109 | if (pos == ii.position && !ii.scrolling) {
1110 | mItems.remove(itemIndex);
1111 | if(pos >=0)
1112 | mAdapter.destroyItem(this, pos % N, ii.object);
1113 | else {
1114 | int pPos;
1115 | if((pos % mAdapter.getCount()) != 0)
1116 | pPos = mAdapter.getCount() - (-pos)%mAdapter.getCount();
1117 | else
1118 | pPos = 0;
1119 | mAdapter.destroyItem(this, pPos, ii.object);
1120 | }
1121 |
1122 | if (DEBUG) {
1123 | Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
1124 | " view: " + ((View) ii.object));
1125 | }
1126 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
1127 | }
1128 | } else if (ii != null && pos == ii.position) {
1129 | extraWidthRight += ii.widthFactor;
1130 | itemIndex++;
1131 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
1132 | } else {
1133 | ii = addNewItem(pos, itemIndex);
1134 | itemIndex++;
1135 | extraWidthRight += ii.widthFactor;
1136 | ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
1137 | }
1138 | }
1139 | }
1140 |
1141 | calculatePageOffsets(curItem, curIndex, oldCurInfo);
1142 | }
1143 |
1144 | if (DEBUG) {
1145 | Log.i(TAG, "Current page list:");
1146 | for (int i=0; i();
1194 | } else {
1195 | mDrawingOrderedChildren.clear();
1196 | }
1197 | final int childCount = getChildCount();
1198 | for (int i = 0; i < childCount; i++) {
1199 | final View child = getChildAt(i);
1200 | mDrawingOrderedChildren.add(child);
1201 | }
1202 | Collections.sort(mDrawingOrderedChildren, sPositionComparator);
1203 | }
1204 | }
1205 |
1206 | private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
1207 | final int N = mAdapter.getCount();
1208 | final int width = getClientWidth();
1209 | final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
1210 | // Fix up offsets for later layout.
1211 | if (oldCurInfo != null) {
1212 | final int oldCurPosition = oldCurInfo.position;
1213 | // Base offsets off of oldCurInfo.
1214 | if (oldCurPosition < curItem.position) {
1215 | int itemIndex = 0;
1216 | ItemInfo ii = null;
1217 | float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
1218 | for (int pos = oldCurPosition + 1;
1219 | pos <= curItem.position && itemIndex < mItems.size(); pos++) {
1220 | ii = mItems.get(itemIndex);
1221 | while (pos > ii.position && itemIndex < mItems.size() - 1) {
1222 | itemIndex++;
1223 | ii = mItems.get(itemIndex);
1224 | }
1225 | while (pos < ii.position) {
1226 | // We don't have an item populated for this,
1227 | // ask the adapter for an offset.
1228 | offset += mAdapter.getPageWidth(pos) + marginOffset;
1229 | pos++;
1230 | }
1231 | ii.offset = offset;
1232 | offset += ii.widthFactor + marginOffset;
1233 | }
1234 | } else if (oldCurPosition > curItem.position) {
1235 | int itemIndex = mItems.size() - 1;
1236 | ItemInfo ii = null;
1237 | float offset = oldCurInfo.offset;
1238 | for (int pos = oldCurPosition - 1;
1239 | pos >= curItem.position && itemIndex >= 0; pos--) {
1240 | ii = mItems.get(itemIndex);
1241 | while (pos < ii.position && itemIndex > 0) {
1242 | itemIndex--;
1243 | ii = mItems.get(itemIndex);
1244 | }
1245 | while (pos > ii.position) {
1246 | // We don't have an item populated for this,
1247 | // ask the adapter for an offset.
1248 | offset -= mAdapter.getPageWidth(pos) + marginOffset;
1249 | pos--;
1250 | }
1251 | offset -= ii.widthFactor + marginOffset;
1252 | ii.offset = offset;
1253 | }
1254 | }
1255 | }
1256 |
1257 | // Base all offsets off of curItem.
1258 | final int itemCount = mItems.size();
1259 | float offset = curItem.offset;
1260 | int pos = curItem.position - 1;
1261 |
1262 | // Previous pages
1263 | for (int i = curIndex - 1; i >= 0; i--, pos--) {
1264 | final ItemInfo ii = mItems.get(i);
1265 | while (pos > ii.position) {
1266 | offset -= mAdapter.getPageWidth(pos--) + marginOffset;
1267 | }
1268 | offset -= ii.widthFactor + marginOffset;
1269 | ii.offset = offset;
1270 | }
1271 | offset = curItem.offset + curItem.widthFactor + marginOffset;
1272 | pos = curItem.position + 1;
1273 | // Next pages
1274 | for (int i = curIndex + 1; i < itemCount; i++, pos++) {
1275 | final ItemInfo ii = mItems.get(i);
1276 | while (pos < ii.position) {
1277 | offset += mAdapter.getPageWidth(pos++) + marginOffset;
1278 | }
1279 |
1280 | ii.offset = offset;
1281 | offset += ii.widthFactor + marginOffset;
1282 | }
1283 |
1284 | mNeedCalculatePageOffsets = false;
1285 | }
1286 |
1287 | /**
1288 | * This is the persistent state that is saved by ViewPager. Only needed
1289 | * if you are creating a sublass of ViewPager that must save its own
1290 | * state, in which case it should implement a subclass of this which
1291 | * contains that state.
1292 | */
1293 | public static class SavedState extends BaseSavedState {
1294 | int position;
1295 | Parcelable adapterState;
1296 | ClassLoader loader;
1297 |
1298 | public SavedState(Parcelable superState) {
1299 | super(superState);
1300 | }
1301 |
1302 | @Override
1303 | public void writeToParcel(Parcel out, int flags) {
1304 | super.writeToParcel(out, flags);
1305 | out.writeInt(position);
1306 | out.writeParcelable(adapterState, flags);
1307 | }
1308 |
1309 | @Override
1310 | public String toString() {
1311 | return "FragmentPager.SavedState{"
1312 | + Integer.toHexString(System.identityHashCode(this))
1313 | + " position=" + position + "}";
1314 | }
1315 |
1316 | public static final Creator CREATOR
1317 | = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks() {
1318 | @Override
1319 | public SavedState createFromParcel(Parcel in, ClassLoader loader) {
1320 | return new SavedState(in, loader);
1321 | }
1322 |
1323 | @Override
1324 | public SavedState[] newArray(int size) {
1325 | return new SavedState[size];
1326 | }
1327 | });
1328 |
1329 | SavedState(Parcel in, ClassLoader loader) {
1330 | super(in);
1331 | if (loader == null) {
1332 | loader = getClass().getClassLoader();
1333 | }
1334 | position = in.readInt();
1335 | adapterState = in.readParcelable(loader);
1336 | this.loader = loader;
1337 | }
1338 | }
1339 |
1340 | @Override
1341 | public Parcelable onSaveInstanceState() {
1342 | Parcelable superState = super.onSaveInstanceState();
1343 | SavedState ss = new SavedState(superState);
1344 | ss.position = mCurItem;
1345 | if (mAdapter != null) {
1346 | ss.adapterState = mAdapter.saveState();
1347 | }
1348 | return ss;
1349 | }
1350 |
1351 | @Override
1352 | public void onRestoreInstanceState(Parcelable state) {
1353 | if (!(state instanceof SavedState)) {
1354 | super.onRestoreInstanceState(state);
1355 | return;
1356 | }
1357 |
1358 | SavedState ss = (SavedState)state;
1359 | super.onRestoreInstanceState(ss.getSuperState());
1360 |
1361 | if (mAdapter != null) {
1362 | mAdapter.restoreState(ss.adapterState, ss.loader);
1363 | setCurrentItemInternal(ss.position, false, true);
1364 | } else {
1365 | mRestoredCurItem = ss.position;
1366 | mRestoredAdapterState = ss.adapterState;
1367 | mRestoredClassLoader = ss.loader;
1368 | }
1369 | }
1370 |
1371 | @Override
1372 | public void addView(View child, int index, ViewGroup.LayoutParams params) {
1373 | if (!checkLayoutParams(params)) {
1374 | params = generateLayoutParams(params);
1375 | }
1376 | final LayoutParams lp = (LayoutParams) params;
1377 | lp.isDecor |= child instanceof Decor;
1378 | if (mInLayout) {
1379 | if (lp != null && lp.isDecor) {
1380 | throw new IllegalStateException("Cannot add pager decor view during layout");
1381 | }
1382 | lp.needsMeasure = true;
1383 | addViewInLayout(child, index, params);
1384 | } else {
1385 | super.addView(child, index, params);
1386 | }
1387 |
1388 | if (USE_CACHE) {
1389 | if (child.getVisibility() != GONE) {
1390 | child.setDrawingCacheEnabled(mScrollingCacheEnabled);
1391 | } else {
1392 | child.setDrawingCacheEnabled(false);
1393 | }
1394 | }
1395 | }
1396 |
1397 | @Override
1398 | public void removeView(View view) {
1399 | if (mInLayout) {
1400 | removeViewInLayout(view);
1401 | } else {
1402 | super.removeView(view);
1403 | }
1404 | }
1405 |
1406 | ItemInfo infoForChild(View child) {
1407 | for (int i=0; i 0 && !mItems.isEmpty()) {
1550 | final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin;
1551 | final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight()
1552 | + oldMargin;
1553 | final int xpos = getScrollX();
1554 | final float pageOffset = (float) xpos / oldWidthWithMargin;
1555 | final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
1556 |
1557 | scrollTo(newOffsetPixels, getScrollY());
1558 | if (!mScroller.isFinished()) {
1559 | // We now return to your regularly scheduled scroll, already in progress.
1560 | final int newDuration = mScroller.getDuration() - mScroller.timePassed();
1561 | ItemInfo targetInfo = infoForPosition(mCurItem);
1562 | mScroller.startScroll(newOffsetPixels, 0,
1563 | (int) (targetInfo.offset * width), 0, newDuration);
1564 | }
1565 | } else {
1566 | final ItemInfo ii = infoForPosition(mCurItem);
1567 | final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
1568 | final int scrollPos = (int) (scrollOffset *
1569 | (width - getPaddingLeft() - getPaddingRight()));
1570 | if (scrollPos != getScrollX()) {
1571 | completeScroll(false);
1572 | scrollTo(scrollPos, getScrollY());
1573 | }
1574 | }
1575 | }
1576 |
1577 | @Override
1578 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
1579 | final int count = getChildCount();
1580 | int width = r - l;
1581 | int height = b - t;
1582 | int paddingLeft = getPaddingLeft();
1583 | int paddingTop = getPaddingTop();
1584 | int paddingRight = getPaddingRight();
1585 | int paddingBottom = getPaddingBottom();
1586 | final int scrollX = getScrollX();
1587 |
1588 | int decorCount = 0;
1589 |
1590 | // First pass - decor views. We need to do this in two passes so that
1591 | // we have the proper offsets for non-decor views later.
1592 | for (int i = 0; i < count; i++) {
1593 | final View child = getChildAt(i);
1594 | if (child.getVisibility() != GONE) {
1595 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1596 | int childLeft = 0;
1597 | int childTop = 0;
1598 | if (lp.isDecor) {
1599 | final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1600 | final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1601 | switch (hgrav) {
1602 | default:
1603 | childLeft = paddingLeft;
1604 | break;
1605 | case Gravity.LEFT:
1606 | childLeft = paddingLeft;
1607 | paddingLeft += child.getMeasuredWidth();
1608 | break;
1609 | case Gravity.CENTER_HORIZONTAL:
1610 | childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1611 | paddingLeft);
1612 | break;
1613 | case Gravity.RIGHT:
1614 | childLeft = width - paddingRight - child.getMeasuredWidth();
1615 | paddingRight += child.getMeasuredWidth();
1616 | break;
1617 | }
1618 | switch (vgrav) {
1619 | default:
1620 | childTop = paddingTop;
1621 | break;
1622 | case Gravity.TOP:
1623 | childTop = paddingTop;
1624 | paddingTop += child.getMeasuredHeight();
1625 | break;
1626 | case Gravity.CENTER_VERTICAL:
1627 | childTop = Math.max((height - child.getMeasuredHeight()) / 2,
1628 | paddingTop);
1629 | break;
1630 | case Gravity.BOTTOM:
1631 | childTop = height - paddingBottom - child.getMeasuredHeight();
1632 | paddingBottom += child.getMeasuredHeight();
1633 | break;
1634 | }
1635 | childLeft += scrollX;
1636 | child.layout(childLeft, childTop,
1637 | childLeft + child.getMeasuredWidth(),
1638 | childTop + child.getMeasuredHeight());
1639 | decorCount++;
1640 | }
1641 | }
1642 | }
1643 |
1644 | final int childWidth = width - paddingLeft - paddingRight;
1645 | // Page views. Do this once we have the right padding offsets from above.
1646 | for (int i = 0; i < count; i++) {
1647 | final View child = getChildAt(i);
1648 | if (child.getVisibility() != GONE) {
1649 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1650 | ItemInfo ii;
1651 | if (!lp.isDecor && (ii = infoForChild(child)) != null) {
1652 | int loff = (int) (childWidth * ii.offset);
1653 | int childLeft = paddingLeft + loff;
1654 | int childTop = paddingTop;
1655 | if (lp.needsMeasure) {
1656 | // This was added during layout and needs measurement.
1657 | // Do it now that we know what we're working with.
1658 | lp.needsMeasure = false;
1659 | final int widthSpec = MeasureSpec.makeMeasureSpec(
1660 | (int) (childWidth * lp.widthFactor),
1661 | MeasureSpec.EXACTLY);
1662 | final int heightSpec = MeasureSpec.makeMeasureSpec(
1663 | (int) (height - paddingTop - paddingBottom),
1664 | MeasureSpec.EXACTLY);
1665 | child.measure(widthSpec, heightSpec);
1666 | }
1667 | if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
1668 | + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
1669 | + "x" + child.getMeasuredHeight());
1670 | child.layout(childLeft, childTop,
1671 | childLeft + child.getMeasuredWidth(),
1672 | childTop + child.getMeasuredHeight());
1673 | }
1674 | }
1675 | }
1676 | mTopPageBounds = paddingTop;
1677 | mBottomPageBounds = height - paddingBottom;
1678 | mDecorChildCount = decorCount;
1679 |
1680 | if (mFirstLayout) {
1681 | scrollToItem(mCurItem, false, 0, false);
1682 | }
1683 | mFirstLayout = false;
1684 | }
1685 |
1686 | @Override
1687 | public void computeScroll() {
1688 | if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
1689 | int oldX = getScrollX();
1690 | int oldY = getScrollY();
1691 | int x = mScroller.getCurrX();
1692 | int y = mScroller.getCurrY();
1693 |
1694 | if (oldX != x || oldY != y) {
1695 | scrollTo(x, y);
1696 | if (!pageScrolled(x)) {
1697 | mScroller.abortAnimation();
1698 | scrollTo(0, y);
1699 | }
1700 | }
1701 |
1702 | // Keep on drawing until the animation has finished.
1703 | ViewCompat.postInvalidateOnAnimation(this);
1704 | return;
1705 | }
1706 |
1707 | // Done with scroll, clean up state.
1708 | completeScroll(true);
1709 | }
1710 |
1711 | private boolean pageScrolled(int xpos) {
1712 | if (mItems.size() == 0) {
1713 | mCalledSuper = false;
1714 | onPageScrolled(0, 0, 0);
1715 | if (!mCalledSuper) {
1716 | throw new IllegalStateException(
1717 | "onPageScrolled did not call superclass implementation");
1718 | }
1719 | return false;
1720 | }
1721 | final ItemInfo ii = infoForCurrentScrollPosition();
1722 | final int width = getClientWidth();
1723 | final int widthWithMargin = width + mPageMargin;
1724 | final float marginOffset = (float) mPageMargin / width;
1725 | final int currentPage = ii.position;
1726 | final float pageOffset = (((float) xpos / width) - ii.offset) /
1727 | (ii.widthFactor + marginOffset);
1728 | final int offsetPixels = (int) (pageOffset * widthWithMargin);
1729 |
1730 | mCalledSuper = false;
1731 | onPageScrolled(currentPage, pageOffset, offsetPixels);
1732 | if (!mCalledSuper) {
1733 | throw new IllegalStateException(
1734 | "onPageScrolled did not call superclass implementation");
1735 | }
1736 | return true;
1737 | }
1738 |
1739 | /**
1740 | * This method will be invoked when the current page is scrolled, either as part
1741 | * of a programmatically initiated smooth scroll or a user initiated touch scroll.
1742 | * If you override this method you must call through to the superclass implementation
1743 | * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
1744 | * returns.
1745 | *
1746 | * @param position Position index of the first page currently being displayed.
1747 | * Page position+1 will be visible if positionOffset is nonzero.
1748 | * @param offset Value from [0, 1) indicating the offset from the page at position.
1749 | * @param offsetPixels Value in pixels indicating the offset from position.
1750 | */
1751 | protected void onPageScrolled(int position, float offset, int offsetPixels) {
1752 | // Offset any decor views if needed - keep them on-screen at all times.
1753 | if (mDecorChildCount > 0) {
1754 | final int scrollX = getScrollX();
1755 | int paddingLeft = getPaddingLeft();
1756 | int paddingRight = getPaddingRight();
1757 | final int width = getWidth();
1758 | final int childCount = getChildCount();
1759 | for (int i = 0; i < childCount; i++) {
1760 | final View child = getChildAt(i);
1761 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1762 | if (!lp.isDecor) continue;
1763 |
1764 | final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1765 | int childLeft = 0;
1766 | switch (hgrav) {
1767 | default:
1768 | childLeft = paddingLeft;
1769 | break;
1770 | case Gravity.LEFT:
1771 | childLeft = paddingLeft;
1772 | paddingLeft += child.getWidth();
1773 | break;
1774 | case Gravity.CENTER_HORIZONTAL:
1775 | childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1776 | paddingLeft);
1777 | break;
1778 | case Gravity.RIGHT:
1779 | childLeft = width - paddingRight - child.getMeasuredWidth();
1780 | paddingRight += child.getMeasuredWidth();
1781 | break;
1782 | }
1783 | childLeft += scrollX;
1784 |
1785 | final int childOffset = childLeft - child.getLeft();
1786 | if (childOffset != 0) {
1787 | child.offsetLeftAndRight(childOffset);
1788 | }
1789 | }
1790 | }
1791 |
1792 | if (mOnPageChangeListener != null) {
1793 | mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1794 | }
1795 | if (mInternalPageChangeListener != null) {
1796 | mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1797 | }
1798 |
1799 | if (mPageTransformer != null) {
1800 | final int scrollX = getScrollX();
1801 | final int childCount = getChildCount();
1802 | for (int i = 0; i < childCount; i++) {
1803 | final View child = getChildAt(i);
1804 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1805 |
1806 | if (lp.isDecor) continue;
1807 |
1808 | final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
1809 | mPageTransformer.transformPage(child, transformPos);
1810 | }
1811 | }
1812 |
1813 | mCalledSuper = true;
1814 | }
1815 |
1816 | private void completeScroll(boolean postEvents) {
1817 | boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
1818 | if (needPopulate) {
1819 | // Done with scroll, no longer want to cache view drawing.
1820 | setScrollingCacheEnabled(false);
1821 | mScroller.abortAnimation();
1822 | int oldX = getScrollX();
1823 | int oldY = getScrollY();
1824 | int x = mScroller.getCurrX();
1825 | int y = mScroller.getCurrY();
1826 | if (oldX != x || oldY != y) {
1827 | scrollTo(x, y);
1828 | if (x != oldX) {
1829 | pageScrolled(x);
1830 | }
1831 | }
1832 | }
1833 | mPopulatePending = false;
1834 | for (int i=0; i 0) || (x > getWidth() - mGutterSize && dx < 0);
1852 | }
1853 |
1854 | private void enableLayers(boolean enable) {
1855 | final int childCount = getChildCount();
1856 | for (int i = 0; i < childCount; i++) {
1857 | final int layerType = enable ?
1858 | ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
1859 | ViewCompat.setLayerType(getChildAt(i), layerType, null);
1860 | }
1861 | }
1862 |
1863 | @Override
1864 | public boolean onInterceptTouchEvent(MotionEvent ev) {
1865 | /*
1866 | * This method JUST determines whether we want to intercept the motion.
1867 | * If we return true, onMotionEvent will be called and we do the actual
1868 | * scrolling there.
1869 | */
1870 |
1871 | final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
1872 |
1873 | // Always take care of the touch gesture being complete.
1874 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1875 | // Release the drag.
1876 | if (DEBUG) Log.v(TAG, "Intercept done!");
1877 | mIsBeingDragged = false;
1878 | mIsUnableToDrag = false;
1879 | mActivePointerId = INVALID_POINTER;
1880 | if (mVelocityTracker != null) {
1881 | mVelocityTracker.recycle();
1882 | mVelocityTracker = null;
1883 | }
1884 | return false;
1885 | }
1886 |
1887 | // Nothing more to do here if we have decided whether or not we
1888 | // are dragging.
1889 | if (action != MotionEvent.ACTION_DOWN) {
1890 | if (mIsBeingDragged) {
1891 | if (DEBUG) Log.v(TAG, "Intercept returning true!");
1892 | return true;
1893 | }
1894 | if (mIsUnableToDrag) {
1895 | if (DEBUG) Log.v(TAG, "Intercept returning false!");
1896 | return false;
1897 | }
1898 | }
1899 |
1900 | switch (action) {
1901 | case MotionEvent.ACTION_MOVE: {
1902 | /*
1903 | * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1904 | * whether the user has moved far enough from his original down touch.
1905 | */
1906 |
1907 | /*
1908 | * Locally do absolute value. mLastMotionY is set to the y value
1909 | * of the down event.
1910 | */
1911 | final int activePointerId = mActivePointerId;
1912 | if (activePointerId == INVALID_POINTER) {
1913 | // If we don't have a valid id, the touch down wasn't on content.
1914 | break;
1915 | }
1916 |
1917 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
1918 | final float x = MotionEventCompat.getX(ev, pointerIndex);
1919 | final float dx = x - mLastMotionX;
1920 | final float xDiff = Math.abs(dx);
1921 | final float y = MotionEventCompat.getY(ev, pointerIndex);
1922 | final float yDiff = Math.abs(y - mInitialMotionY);
1923 | if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1924 |
1925 | if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
1926 | canScroll(this, false, (int) dx, (int) x, (int) y)) {
1927 | // Nested view has scrollable area under this point. Let it be handled there.
1928 | mLastMotionX = x;
1929 | mLastMotionY = y;
1930 | mIsUnableToDrag = true;
1931 | return false;
1932 | }
1933 | if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
1934 | if (DEBUG) Log.v(TAG, "Starting drag!");
1935 | mIsBeingDragged = true;
1936 | requestParentDisallowInterceptTouchEvent(true);
1937 | setScrollState(SCROLL_STATE_DRAGGING);
1938 | mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
1939 | mInitialMotionX - mTouchSlop;
1940 | mLastMotionY = y;
1941 | setScrollingCacheEnabled(true);
1942 | } else if (yDiff > mTouchSlop) {
1943 | // The finger has moved enough in the vertical
1944 | // direction to be counted as a drag... abort
1945 | // any attempt to drag horizontally, to work correctly
1946 | // with children that have scrolling containers.
1947 | if (DEBUG) Log.v(TAG, "Starting unable to drag!");
1948 | mIsUnableToDrag = true;
1949 | }
1950 | if (mIsBeingDragged) {
1951 | // Scroll to follow the motion event
1952 | if (performDrag(x)) {
1953 | ViewCompat.postInvalidateOnAnimation(this);
1954 | }
1955 | }
1956 | break;
1957 | }
1958 |
1959 | case MotionEvent.ACTION_DOWN: {
1960 | /*
1961 | * Remember location of down touch.
1962 | * ACTION_DOWN always refers to pointer index 0.
1963 | */
1964 | mLastMotionX = mInitialMotionX = ev.getX();
1965 | mLastMotionY = mInitialMotionY = ev.getY();
1966 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1967 | mIsUnableToDrag = false;
1968 |
1969 | mScroller.computeScrollOffset();
1970 | if (mScrollState == SCROLL_STATE_SETTLING &&
1971 | Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
1972 | // Let the user 'catch' the pager as it animates.
1973 | mScroller.abortAnimation();
1974 | mPopulatePending = false;
1975 | populate();
1976 | mIsBeingDragged = true;
1977 | requestParentDisallowInterceptTouchEvent(true);
1978 | setScrollState(SCROLL_STATE_DRAGGING);
1979 | } else {
1980 | completeScroll(false);
1981 | mIsBeingDragged = false;
1982 | }
1983 |
1984 | if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
1985 | + " mIsBeingDragged=" + mIsBeingDragged
1986 | + "mIsUnableToDrag=" + mIsUnableToDrag);
1987 | break;
1988 | }
1989 |
1990 | case MotionEventCompat.ACTION_POINTER_UP:
1991 | onSecondaryPointerUp(ev);
1992 | break;
1993 | }
1994 |
1995 | if (mVelocityTracker == null) {
1996 | mVelocityTracker = VelocityTracker.obtain();
1997 | }
1998 | mVelocityTracker.addMovement(ev);
1999 |
2000 | /*
2001 | * The only time we want to intercept motion events is if we are in the
2002 | * drag mode.
2003 | */
2004 | return mIsBeingDragged;
2005 | }
2006 |
2007 | @Override
2008 | public boolean onTouchEvent(MotionEvent ev) {
2009 | if (mFakeDragging) {
2010 | // A fake drag is in progress already, ignore this real one
2011 | // but still eat the touch events.
2012 | // (It is likely that the user is multi-touching the screen.)
2013 | return true;
2014 | }
2015 |
2016 | if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
2017 | // Don't handle edge touches immediately -- they may actually belong to one of our
2018 | // descendants.
2019 | return false;
2020 | }
2021 |
2022 | if (mAdapter == null || mAdapter.getCount() == 0) {
2023 | // Nothing to present or scroll; nothing to touch.
2024 | return false;
2025 | }
2026 |
2027 | if (mVelocityTracker == null) {
2028 | mVelocityTracker = VelocityTracker.obtain();
2029 | }
2030 | mVelocityTracker.addMovement(ev);
2031 |
2032 | final int action = ev.getAction();
2033 | boolean needsInvalidate = false;
2034 |
2035 | switch (action & MotionEventCompat.ACTION_MASK) {
2036 | case MotionEvent.ACTION_DOWN: {
2037 | mScroller.abortAnimation();
2038 | mPopulatePending = false;
2039 | populate();
2040 |
2041 | // Remember where the motion event started
2042 | mLastMotionX = mInitialMotionX = ev.getX();
2043 | mLastMotionY = mInitialMotionY = ev.getY();
2044 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
2045 | break;
2046 | }
2047 | case MotionEvent.ACTION_MOVE:
2048 | if (!mIsBeingDragged) {
2049 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
2050 | final float x = MotionEventCompat.getX(ev, pointerIndex);
2051 | final float xDiff = Math.abs(x - mLastMotionX);
2052 | final float y = MotionEventCompat.getY(ev, pointerIndex);
2053 | final float yDiff = Math.abs(y - mLastMotionY);
2054 | if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
2055 | if (xDiff > mTouchSlop && xDiff > yDiff) {
2056 | if (DEBUG) Log.v(TAG, "Starting drag!");
2057 | mIsBeingDragged = true;
2058 | requestParentDisallowInterceptTouchEvent(true);
2059 | mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
2060 | mInitialMotionX - mTouchSlop;
2061 | mLastMotionY = y;
2062 | setScrollState(SCROLL_STATE_DRAGGING);
2063 | setScrollingCacheEnabled(true);
2064 |
2065 | // Disallow Parent Intercept, just in case
2066 | ViewParent parent = getParent();
2067 | if (parent != null) {
2068 | parent.requestDisallowInterceptTouchEvent(true);
2069 | }
2070 | }
2071 | }
2072 | // Not else! Note that mIsBeingDragged can be set above.
2073 | if (mIsBeingDragged) {
2074 | // Scroll to follow the motion event
2075 | final int activePointerIndex = MotionEventCompat.findPointerIndex(
2076 | ev, mActivePointerId);
2077 | final float x = MotionEventCompat.getX(ev, activePointerIndex);
2078 | needsInvalidate |= performDrag(x);
2079 | }
2080 | break;
2081 | case MotionEvent.ACTION_UP:
2082 | if (mIsBeingDragged) {
2083 | final VelocityTracker velocityTracker = mVelocityTracker;
2084 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2085 | int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
2086 | velocityTracker, mActivePointerId);
2087 | mPopulatePending = true;
2088 | final int width = getClientWidth();
2089 | final int scrollX = getScrollX();
2090 | final ItemInfo ii = infoForCurrentScrollPosition();
2091 | final int currentPage = ii.position;
2092 | final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
2093 | final int activePointerIndex =
2094 | MotionEventCompat.findPointerIndex(ev, mActivePointerId);
2095 | final float x = MotionEventCompat.getX(ev, activePointerIndex);
2096 | final int totalDelta = (int) (x - mInitialMotionX);
2097 | int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
2098 | totalDelta);
2099 | setCurrentItemInternal(nextPage, true, true, initialVelocity);
2100 |
2101 | mActivePointerId = INVALID_POINTER;
2102 | endDrag();
2103 | needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
2104 | }
2105 | break;
2106 | case MotionEvent.ACTION_CANCEL:
2107 | if (mIsBeingDragged) {
2108 | scrollToItem(mCurItem, true, 0, false);
2109 | mActivePointerId = INVALID_POINTER;
2110 | endDrag();
2111 | needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
2112 | }
2113 | break;
2114 | case MotionEventCompat.ACTION_POINTER_DOWN: {
2115 | final int index = MotionEventCompat.getActionIndex(ev);
2116 | final float x = MotionEventCompat.getX(ev, index);
2117 | mLastMotionX = x;
2118 | mActivePointerId = MotionEventCompat.getPointerId(ev, index);
2119 | break;
2120 | }
2121 | case MotionEventCompat.ACTION_POINTER_UP:
2122 | onSecondaryPointerUp(ev);
2123 | mLastMotionX = MotionEventCompat.getX(ev,
2124 | MotionEventCompat.findPointerIndex(ev, mActivePointerId));
2125 | break;
2126 | }
2127 | if (needsInvalidate) {
2128 | ViewCompat.postInvalidateOnAnimation(this);
2129 | }
2130 | return true;
2131 | }
2132 |
2133 | private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
2134 | final ViewParent parent = getParent();
2135 | if (parent != null) {
2136 | parent.requestDisallowInterceptTouchEvent(disallowIntercept);
2137 | }
2138 | }
2139 |
2140 | private boolean performDrag(float x) {
2141 | boolean needsInvalidate = false;
2142 |
2143 | final float deltaX = mLastMotionX - x;
2144 | mLastMotionX = x;
2145 |
2146 | float oldScrollX = getScrollX();
2147 | float scrollX = oldScrollX + deltaX;
2148 | final int width = getClientWidth();
2149 |
2150 | float leftBound = width * mFirstOffset;
2151 | float rightBound = width * mLastOffset;
2152 | boolean leftAbsolute = true;
2153 | boolean rightAbsolute = true;
2154 |
2155 | final ItemInfo firstItem = mItems.get(0);
2156 | final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2157 | if (firstItem.position != 0) {
2158 | leftAbsolute = false;
2159 | leftBound = firstItem.offset * width;
2160 | }
2161 | if (lastItem.position != mAdapter.getCount() - 1) {
2162 | rightAbsolute = false;
2163 | rightBound = lastItem.offset * width;
2164 | }
2165 |
2166 | if (scrollX < leftBound) {
2167 | if (leftAbsolute) {
2168 | float over = leftBound - scrollX;
2169 | needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);
2170 | }
2171 | scrollX = leftBound;
2172 | } else if (scrollX > rightBound) {
2173 | if (rightAbsolute) {
2174 | float over = scrollX - rightBound;
2175 | needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);
2176 | }
2177 | scrollX = rightBound;
2178 | }
2179 | // Don't lose the rounded component
2180 | mLastMotionX += scrollX - (int) scrollX;
2181 | scrollTo((int) scrollX, getScrollY());
2182 | pageScrolled((int) scrollX);
2183 |
2184 | return needsInvalidate;
2185 | }
2186 |
2187 | /**
2188 | * @return Info about the page at the current scroll position.
2189 | * This can be synthetic for a missing middle page; the 'object' field can be null.
2190 | */
2191 | private ItemInfo infoForCurrentScrollPosition() {
2192 | final int width = getClientWidth();
2193 | final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
2194 | final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
2195 | int lastPos = -1;
2196 | float lastOffset = 0.f;
2197 | float lastWidth = 0.f;
2198 | boolean first = true;
2199 |
2200 | ItemInfo lastItem = null;
2201 | for (int i = 0; i < mItems.size(); i++) {
2202 | ItemInfo ii = mItems.get(i);
2203 | float offset;
2204 | if (!first && ii.position != lastPos + 1) {
2205 | // Create a synthetic item for a missing page.
2206 | ii = mTempItem;
2207 | ii.offset = lastOffset + lastWidth + marginOffset;
2208 | ii.position = lastPos + 1;
2209 | ii.widthFactor = mAdapter.getPageWidth(ii.position);
2210 | i--;
2211 | }
2212 | offset = ii.offset;
2213 |
2214 | final float leftBound = offset;
2215 | final float rightBound = offset + ii.widthFactor + marginOffset;
2216 | if (first || scrollOffset >= leftBound) {
2217 | if (scrollOffset < rightBound || i == mItems.size() - 1) {
2218 | return ii;
2219 | }
2220 | } else {
2221 | return lastItem;
2222 | }
2223 | first = false;
2224 | lastPos = ii.position;
2225 | lastOffset = offset;
2226 | lastWidth = ii.widthFactor;
2227 | lastItem = ii;
2228 | }
2229 |
2230 | return lastItem;
2231 | }
2232 |
2233 | private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
2234 | int targetPage;
2235 | if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
2236 | targetPage = velocity > 0 ? currentPage : currentPage + 1;
2237 | } else {
2238 | final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
2239 | targetPage = (int) (currentPage + pageOffset + truncator);
2240 | }
2241 |
2242 | if (mItems.size() > 0) {
2243 | final ItemInfo firstItem = mItems.get(0);
2244 | final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2245 |
2246 | // Only let the user target pages we have items for
2247 | targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
2248 | }
2249 |
2250 | return targetPage;
2251 | }
2252 |
2253 | @Override
2254 | public void draw(Canvas canvas) {
2255 | super.draw(canvas);
2256 | boolean needsInvalidate = false;
2257 |
2258 | final int overScrollMode = ViewCompat.getOverScrollMode(this);
2259 | if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
2260 | (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
2261 | mAdapter != null && mAdapter.getCount() > 1)) {
2262 | if (!mLeftEdge.isFinished()) {
2263 | final int restoreCount = canvas.save();
2264 | final int height = getHeight() - getPaddingTop() - getPaddingBottom();
2265 | final int width = getWidth();
2266 |
2267 | canvas.rotate(270);
2268 | canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
2269 | mLeftEdge.setSize(height, width);
2270 | needsInvalidate |= mLeftEdge.draw(canvas);
2271 | canvas.restoreToCount(restoreCount);
2272 | }
2273 | if (!mRightEdge.isFinished()) {
2274 | final int restoreCount = canvas.save();
2275 | final int width = getWidth();
2276 | final int height = getHeight() - getPaddingTop() - getPaddingBottom();
2277 |
2278 | canvas.rotate(90);
2279 | canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
2280 | mRightEdge.setSize(height, width);
2281 | needsInvalidate |= mRightEdge.draw(canvas);
2282 | canvas.restoreToCount(restoreCount);
2283 | }
2284 | } else {
2285 | mLeftEdge.finish();
2286 | mRightEdge.finish();
2287 | }
2288 |
2289 | if (needsInvalidate) {
2290 | // Keep animating
2291 | ViewCompat.postInvalidateOnAnimation(this);
2292 | }
2293 | }
2294 |
2295 | @Override
2296 | protected void onDraw(Canvas canvas) {
2297 | super.onDraw(canvas);
2298 |
2299 | // Draw the margin drawable between pages if needed.
2300 | if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
2301 | final int scrollX = getScrollX();
2302 | final int width = getWidth();
2303 |
2304 | final float marginOffset = (float) mPageMargin / width;
2305 | int itemIndex = 0;
2306 | ItemInfo ii = mItems.get(0);
2307 | float offset = ii.offset;
2308 | final int itemCount = mItems.size();
2309 | final int firstPos = ii.position;
2310 | final int lastPos = mItems.get(itemCount - 1).position;
2311 | for (int pos = firstPos; pos < lastPos; pos++) {
2312 | while (pos > ii.position && itemIndex < itemCount) {
2313 | ii = mItems.get(++itemIndex);
2314 | }
2315 |
2316 | float drawAt;
2317 | if (pos == ii.position) {
2318 | drawAt = (ii.offset + ii.widthFactor) * width;
2319 | offset = ii.offset + ii.widthFactor + marginOffset;
2320 | } else {
2321 | float widthFactor = mAdapter.getPageWidth(pos);
2322 | drawAt = (offset + widthFactor) * width;
2323 | offset += widthFactor + marginOffset;
2324 | }
2325 |
2326 | if (drawAt + mPageMargin > scrollX) {
2327 | mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
2328 | (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
2329 | mMarginDrawable.draw(canvas);
2330 | }
2331 |
2332 | if (drawAt > scrollX + width) {
2333 | break; // No more visible, no sense in continuing
2334 | }
2335 | }
2336 | }
2337 | }
2338 |
2339 | /**
2340 | * Start a fake drag of the pager.
2341 | *
2342 | * A fake drag can be useful if you want to synchronize the motion of the ViewPager
2343 | * with the touch scrolling of another view, while still letting the ViewPager
2344 | * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
2345 | * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
2346 | * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
2347 | *
2348 | *
During a fake drag the ViewPager will ignore all touch events. If a real drag
2349 | * is already in progress, this method will return false.
2350 | *
2351 | * @return true if the fake drag began successfully, false if it could not be started.
2352 | *
2353 | * @see #fakeDragBy(float)
2354 | * @see #endFakeDrag()
2355 | */
2356 | public boolean beginFakeDrag() {
2357 | if (mIsBeingDragged) {
2358 | return false;
2359 | }
2360 | mFakeDragging = true;
2361 | setScrollState(SCROLL_STATE_DRAGGING);
2362 | mInitialMotionX = mLastMotionX = 0;
2363 | if (mVelocityTracker == null) {
2364 | mVelocityTracker = VelocityTracker.obtain();
2365 | } else {
2366 | mVelocityTracker.clear();
2367 | }
2368 | final long time = SystemClock.uptimeMillis();
2369 | final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
2370 | mVelocityTracker.addMovement(ev);
2371 | ev.recycle();
2372 | mFakeDragBeginTime = time;
2373 | return true;
2374 | }
2375 |
2376 | /**
2377 | * End a fake drag of the pager.
2378 | *
2379 | * @see #beginFakeDrag()
2380 | * @see #fakeDragBy(float)
2381 | */
2382 | public void endFakeDrag() {
2383 | if (!mFakeDragging) {
2384 | throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2385 | }
2386 |
2387 | final VelocityTracker velocityTracker = mVelocityTracker;
2388 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2389 | int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
2390 | velocityTracker, mActivePointerId);
2391 | mPopulatePending = true;
2392 | final int width = getClientWidth();
2393 | final int scrollX = getScrollX();
2394 | final ItemInfo ii = infoForCurrentScrollPosition();
2395 | final int currentPage = ii.position;
2396 | final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
2397 | final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
2398 | int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
2399 | totalDelta);
2400 | setCurrentItemInternal(nextPage, true, true, initialVelocity);
2401 | endDrag();
2402 |
2403 | mFakeDragging = false;
2404 | }
2405 |
2406 | /**
2407 | * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
2408 | *
2409 | * @param xOffset Offset in pixels to drag by.
2410 | * @see #beginFakeDrag()
2411 | * @see #endFakeDrag()
2412 | */
2413 | public void fakeDragBy(float xOffset) {
2414 | if (!mFakeDragging) {
2415 | throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2416 | }
2417 |
2418 | mLastMotionX += xOffset;
2419 |
2420 | float oldScrollX = getScrollX();
2421 | float scrollX = oldScrollX - xOffset;
2422 | final int width = getClientWidth();
2423 |
2424 | float leftBound = width * mFirstOffset;
2425 | float rightBound = width * mLastOffset;
2426 |
2427 | final ItemInfo firstItem = mItems.get(0);
2428 | final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2429 | if (firstItem.position != 0) {
2430 | leftBound = firstItem.offset * width;
2431 | }
2432 | if (lastItem.position != mAdapter.getCount() - 1) {
2433 | rightBound = lastItem.offset * width;
2434 | }
2435 |
2436 | if (scrollX < leftBound) {
2437 | scrollX = leftBound;
2438 | } else if (scrollX > rightBound) {
2439 | scrollX = rightBound;
2440 | }
2441 | // Don't lose the rounded component
2442 | mLastMotionX += scrollX - (int) scrollX;
2443 | scrollTo((int) scrollX, getScrollY());
2444 | pageScrolled((int) scrollX);
2445 |
2446 | // Synthesize an event for the VelocityTracker.
2447 | final long time = SystemClock.uptimeMillis();
2448 | final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
2449 | mLastMotionX, 0, 0);
2450 | mVelocityTracker.addMovement(ev);
2451 | ev.recycle();
2452 | }
2453 |
2454 | /**
2455 | * Returns true if a fake drag is in progress.
2456 | *
2457 | * @return true if currently in a fake drag, false otherwise.
2458 | *
2459 | * @see #beginFakeDrag()
2460 | * @see #fakeDragBy(float)
2461 | * @see #endFakeDrag()
2462 | */
2463 | public boolean isFakeDragging() {
2464 | return mFakeDragging;
2465 | }
2466 |
2467 | private void onSecondaryPointerUp(MotionEvent ev) {
2468 | final int pointerIndex = MotionEventCompat.getActionIndex(ev);
2469 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
2470 | if (pointerId == mActivePointerId) {
2471 | // This was our active pointer going up. Choose a new
2472 | // active pointer and adjust accordingly.
2473 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
2474 | mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
2475 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
2476 | if (mVelocityTracker != null) {
2477 | mVelocityTracker.clear();
2478 | }
2479 | }
2480 | }
2481 |
2482 | private void endDrag() {
2483 | mIsBeingDragged = false;
2484 | mIsUnableToDrag = false;
2485 |
2486 | if (mVelocityTracker != null) {
2487 | mVelocityTracker.recycle();
2488 | mVelocityTracker = null;
2489 | }
2490 | }
2491 |
2492 | private void setScrollingCacheEnabled(boolean enabled) {
2493 | if (mScrollingCacheEnabled != enabled) {
2494 | mScrollingCacheEnabled = enabled;
2495 | if (USE_CACHE) {
2496 | final int size = getChildCount();
2497 | for (int i = 0; i < size; ++i) {
2498 | final View child = getChildAt(i);
2499 | if (child.getVisibility() != GONE) {
2500 | child.setDrawingCacheEnabled(enabled);
2501 | }
2502 | }
2503 | }
2504 | }
2505 | }
2506 |
2507 | public boolean canScrollHorizontally(int direction) {
2508 | if (mAdapter == null) {
2509 | return false;
2510 | }
2511 |
2512 | final int width = getClientWidth();
2513 | final int scrollX = getScrollX();
2514 | if (direction < 0) {
2515 | return (scrollX > (int) (width * mFirstOffset));
2516 | } else if (direction > 0) {
2517 | return (scrollX < (int) (width * mLastOffset));
2518 | } else {
2519 | return false;
2520 | }
2521 | }
2522 |
2523 | /**
2524 | * Tests scrollability within child views of v given a delta of dx.
2525 | *
2526 | * @param v View to test for horizontal scrollability
2527 | * @param checkV Whether the view v passed should itself be checked for scrollability (true),
2528 | * or just its children (false).
2529 | * @param dx Delta scrolled in pixels
2530 | * @param x X coordinate of the active touch point
2531 | * @param y Y coordinate of the active touch point
2532 | * @return true if child views of v can be scrolled by delta of dx.
2533 | */
2534 | protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
2535 | if (v instanceof ViewGroup) {
2536 | final ViewGroup group = (ViewGroup) v;
2537 | final int scrollX = v.getScrollX();
2538 | final int scrollY = v.getScrollY();
2539 | final int count = group.getChildCount();
2540 | // Count backwards - let topmost views consume scroll distance first.
2541 | for (int i = count - 1; i >= 0; i--) {
2542 | // TODO: Add versioned support here for transformed views.
2543 | // This will not work for transformed views in Honeycomb+
2544 | final View child = group.getChildAt(i);
2545 | if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
2546 | y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
2547 | canScroll(child, true, dx, x + scrollX - child.getLeft(),
2548 | y + scrollY - child.getTop())) {
2549 | return true;
2550 | }
2551 | }
2552 | }
2553 |
2554 | return checkV && ViewCompat.canScrollHorizontally(v, -dx);
2555 | }
2556 |
2557 | @Override
2558 | public boolean dispatchKeyEvent(KeyEvent event) {
2559 | // Let the focused view and/or our descendants get the key first
2560 | return super.dispatchKeyEvent(event) || executeKeyEvent(event);
2561 | }
2562 |
2563 | /**
2564 | * You can call this function yourself to have the scroll view perform
2565 | * scrolling from a key event, just as if the event had been dispatched to
2566 | * it by the view hierarchy.
2567 | *
2568 | * @param event The key event to execute.
2569 | * @return Return true if the event was handled, else false.
2570 | */
2571 | public boolean executeKeyEvent(KeyEvent event) {
2572 | boolean handled = false;
2573 | if (event.getAction() == KeyEvent.ACTION_DOWN) {
2574 | switch (event.getKeyCode()) {
2575 | case KeyEvent.KEYCODE_DPAD_LEFT:
2576 | handled = arrowScroll(FOCUS_LEFT);
2577 | break;
2578 | case KeyEvent.KEYCODE_DPAD_RIGHT:
2579 | handled = arrowScroll(FOCUS_RIGHT);
2580 | break;
2581 | case KeyEvent.KEYCODE_TAB:
2582 | if (Build.VERSION.SDK_INT >= 11) {
2583 | // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
2584 | // before Android 3.0. Ignore the tab key on those devices.
2585 | if (KeyEventCompat.hasNoModifiers(event)) {
2586 | handled = arrowScroll(FOCUS_FORWARD);
2587 | } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
2588 | handled = arrowScroll(FOCUS_BACKWARD);
2589 | }
2590 | }
2591 | break;
2592 | }
2593 | }
2594 | return handled;
2595 | }
2596 |
2597 | public boolean arrowScroll(int direction) {
2598 | View currentFocused = findFocus();
2599 | if (currentFocused == this) {
2600 | currentFocused = null;
2601 | } else if (currentFocused != null) {
2602 | boolean isChild = false;
2603 | for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
2604 | parent = parent.getParent()) {
2605 | if (parent == this) {
2606 | isChild = true;
2607 | break;
2608 | }
2609 | }
2610 | if (!isChild) {
2611 | // This would cause the focus search down below to fail in fun ways.
2612 | final StringBuilder sb = new StringBuilder();
2613 | sb.append(currentFocused.getClass().getSimpleName());
2614 | for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
2615 | parent = parent.getParent()) {
2616 | sb.append(" => ").append(parent.getClass().getSimpleName());
2617 | }
2618 | Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
2619 | "current focused view " + sb.toString());
2620 | currentFocused = null;
2621 | }
2622 | }
2623 |
2624 | boolean handled = false;
2625 |
2626 | View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
2627 | direction);
2628 | if (nextFocused != null && nextFocused != currentFocused) {
2629 | if (direction == View.FOCUS_LEFT) {
2630 | // If there is nothing to the left, or this is causing us to
2631 | // jump to the right, then what we really want to do is page left.
2632 | final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
2633 | final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
2634 | if (currentFocused != null && nextLeft >= currLeft) {
2635 | handled = pageLeft();
2636 | } else {
2637 | handled = nextFocused.requestFocus();
2638 | }
2639 | } else if (direction == View.FOCUS_RIGHT) {
2640 | // If there is nothing to the right, or this is causing us to
2641 | // jump to the left, then what we really want to do is page right.
2642 | final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
2643 | final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
2644 | if (currentFocused != null && nextLeft <= currLeft) {
2645 | handled = pageRight();
2646 | } else {
2647 | handled = nextFocused.requestFocus();
2648 | }
2649 | }
2650 | } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
2651 | // Trying to move left and nothing there; try to page.
2652 | handled = pageLeft();
2653 | } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
2654 | // Trying to move right and nothing there; try to page.
2655 | handled = pageRight();
2656 | }
2657 | if (handled) {
2658 | playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2659 | }
2660 | return handled;
2661 | }
2662 |
2663 | private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
2664 | if (outRect == null) {
2665 | outRect = new Rect();
2666 | }
2667 | if (child == null) {
2668 | outRect.set(0, 0, 0, 0);
2669 | return outRect;
2670 | }
2671 | outRect.left = child.getLeft();
2672 | outRect.right = child.getRight();
2673 | outRect.top = child.getTop();
2674 | outRect.bottom = child.getBottom();
2675 |
2676 | ViewParent parent = child.getParent();
2677 | while (parent instanceof ViewGroup && parent != this) {
2678 | final ViewGroup group = (ViewGroup) parent;
2679 | outRect.left += group.getLeft();
2680 | outRect.right += group.getRight();
2681 | outRect.top += group.getTop();
2682 | outRect.bottom += group.getBottom();
2683 |
2684 | parent = group.getParent();
2685 | }
2686 | return outRect;
2687 | }
2688 |
2689 | boolean pageLeft() {
2690 | if (mCurItem > 0) {
2691 | setCurrentItem(mCurItem-1, true);
2692 | return true;
2693 | }
2694 | return false;
2695 | }
2696 |
2697 | boolean pageRight() {
2698 | if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
2699 | setCurrentItem(mCurItem+1, true);
2700 | return true;
2701 | }
2702 | return false;
2703 | }
2704 |
2705 | /**
2706 | * We only want the current page that is being shown to be focusable.
2707 | */
2708 | @Override
2709 | public void addFocusables(ArrayList views, int direction, int focusableMode) {
2710 | final int focusableCount = views.size();
2711 |
2712 | final int descendantFocusability = getDescendantFocusability();
2713 |
2714 | if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
2715 | for (int i = 0; i < getChildCount(); i++) {
2716 | final View child = getChildAt(i);
2717 | if (child.getVisibility() == VISIBLE) {
2718 | ItemInfo ii = infoForChild(child);
2719 | if (ii != null && ii.position == mCurItem) {
2720 | child.addFocusables(views, direction, focusableMode);
2721 | }
2722 | }
2723 | }
2724 | }
2725 |
2726 | // we add ourselves (if focusable) in all cases except for when we are
2727 | // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
2728 | // to avoid the focus search finding layouts when a more precise search
2729 | // among the focusable children would be more interesting.
2730 | if (
2731 | descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
2732 | // No focusable descendants
2733 | (focusableCount == views.size())) {
2734 | // Note that we can't call the superclass here, because it will
2735 | // add all views in. So we need to do the same thing View does.
2736 | if (!isFocusable()) {
2737 | return;
2738 | }
2739 | if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
2740 | isInTouchMode() && !isFocusableInTouchMode()) {
2741 | return;
2742 | }
2743 | if (views != null) {
2744 | views.add(this);
2745 | }
2746 | }
2747 | }
2748 |
2749 | /**
2750 | * We only want the current page that is being shown to be touchable.
2751 | */
2752 | @Override
2753 | public void addTouchables(ArrayList views) {
2754 | // Note that we don't call super.addTouchables(), which means that
2755 | // we don't call View.addTouchables(). This is okay because a ViewPager
2756 | // is itself not touchable.
2757 | for (int i = 0; i < getChildCount(); i++) {
2758 | final View child = getChildAt(i);
2759 | if (child.getVisibility() == VISIBLE) {
2760 | ItemInfo ii = infoForChild(child);
2761 | if (ii != null && ii.position == mCurItem) {
2762 | child.addTouchables(views);
2763 | }
2764 | }
2765 | }
2766 | }
2767 |
2768 | /**
2769 | * We only want the current page that is being shown to be focusable.
2770 | */
2771 | @Override
2772 | protected boolean onRequestFocusInDescendants(int direction,
2773 | Rect previouslyFocusedRect) {
2774 | int index;
2775 | int increment;
2776 | int end;
2777 | int count = getChildCount();
2778 | if ((direction & FOCUS_FORWARD) != 0) {
2779 | index = 0;
2780 | increment = 1;
2781 | end = count;
2782 | } else {
2783 | index = count - 1;
2784 | increment = -1;
2785 | end = -1;
2786 | }
2787 | for (int i = index; i != end; i += increment) {
2788 | View child = getChildAt(i);
2789 | if (child.getVisibility() == VISIBLE) {
2790 | ItemInfo ii = infoForChild(child);
2791 | if (ii != null && ii.position == mCurItem) {
2792 | if (child.requestFocus(direction, previouslyFocusedRect)) {
2793 | return true;
2794 | }
2795 | }
2796 | }
2797 | }
2798 | return false;
2799 | }
2800 |
2801 | @Override
2802 | public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
2803 | // Dispatch scroll events from this ViewPager.
2804 | if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
2805 | return super.dispatchPopulateAccessibilityEvent(event);
2806 | }
2807 |
2808 | // Dispatch all other accessibility events from the current page.
2809 | final int childCount = getChildCount();
2810 | for (int i = 0; i < childCount; i++) {
2811 | final View child = getChildAt(i);
2812 | if (child.getVisibility() == VISIBLE) {
2813 | final ItemInfo ii = infoForChild(child);
2814 | if (ii != null && ii.position == mCurItem &&
2815 | child.dispatchPopulateAccessibilityEvent(event)) {
2816 | return true;
2817 | }
2818 | }
2819 | }
2820 |
2821 | return false;
2822 | }
2823 |
2824 | @Override
2825 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
2826 | return new LayoutParams();
2827 | }
2828 |
2829 | @Override
2830 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2831 | return generateDefaultLayoutParams();
2832 | }
2833 |
2834 | @Override
2835 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2836 | return p instanceof LayoutParams && super.checkLayoutParams(p);
2837 | }
2838 |
2839 | @Override
2840 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2841 | return new LayoutParams(getContext(), attrs);
2842 | }
2843 |
2844 | class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
2845 |
2846 | @Override
2847 | public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
2848 | super.onInitializeAccessibilityEvent(host, event);
2849 | event.setClassName(CycleViewPager.class.getName());
2850 | final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
2851 | recordCompat.setScrollable(canScroll());
2852 | if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
2853 | && mAdapter != null) {
2854 | recordCompat.setItemCount(mAdapter.getCount());
2855 | recordCompat.setFromIndex(mCurItem);
2856 | recordCompat.setToIndex(mCurItem);
2857 | }
2858 | }
2859 |
2860 | @Override
2861 | public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
2862 | super.onInitializeAccessibilityNodeInfo(host, info);
2863 | info.setClassName(CycleViewPager.class.getName());
2864 | info.setScrollable(canScroll());
2865 | if (canScrollHorizontally(1)) {
2866 | info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
2867 | }
2868 | if (canScrollHorizontally(-1)) {
2869 | info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
2870 | }
2871 | }
2872 |
2873 | @Override
2874 | public boolean performAccessibilityAction(View host, int action, Bundle args) {
2875 | if (super.performAccessibilityAction(host, action, args)) {
2876 | return true;
2877 | }
2878 | switch (action) {
2879 | case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
2880 | if (canScrollHorizontally(1)) {
2881 | setCurrentItem(mCurItem + 1);
2882 | return true;
2883 | }
2884 | } return false;
2885 | case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
2886 | if (canScrollHorizontally(-1)) {
2887 | setCurrentItem(mCurItem - 1);
2888 | return true;
2889 | }
2890 | } return false;
2891 | }
2892 | return false;
2893 | }
2894 |
2895 | private boolean canScroll() {
2896 | return (mAdapter != null) && (mAdapter.getCount() > 1);
2897 | }
2898 | }
2899 |
2900 | private class PagerObserver extends DataSetObserver {
2901 | @Override
2902 | public void onChanged() {
2903 | dataSetChanged();
2904 | }
2905 | @Override
2906 | public void onInvalidated() {
2907 | dataSetChanged();
2908 | }
2909 | }
2910 |
2911 | /**
2912 | * Layout parameters that should be supplied for views added to a
2913 | * ViewPager.
2914 | */
2915 | public static class LayoutParams extends ViewGroup.LayoutParams {
2916 | /**
2917 | * true if this view is a decoration on the pager itself and not
2918 | * a view supplied by the adapter.
2919 | */
2920 | public boolean isDecor;
2921 |
2922 | /**
2923 | * Gravity setting for use on decor views only:
2924 | * Where to position the view page within the overall ViewPager
2925 | * container; constants are defined in {@link android.view.Gravity}.
2926 | */
2927 | public int gravity;
2928 |
2929 | /**
2930 | * Width as a 0-1 multiplier of the measured pager width
2931 | */
2932 | float widthFactor = 0.f;
2933 |
2934 | /**
2935 | * true if this view was added during layout and needs to be measured
2936 | * before being positioned.
2937 | */
2938 | boolean needsMeasure;
2939 |
2940 | /**
2941 | * Adapter position this view is for if !isDecor
2942 | */
2943 | int position;
2944 |
2945 | /**
2946 | * Current child index within the ViewPager that this view occupies
2947 | */
2948 | int childIndex;
2949 |
2950 | public LayoutParams() {
2951 | super(FILL_PARENT, FILL_PARENT);
2952 | }
2953 |
2954 | public LayoutParams(Context context, AttributeSet attrs) {
2955 | super(context, attrs);
2956 |
2957 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
2958 | gravity = a.getInteger(0, Gravity.TOP);
2959 | a.recycle();
2960 | }
2961 | }
2962 |
2963 | static class ViewPositionComparator implements Comparator {
2964 | @Override
2965 | public int compare(View lhs, View rhs) {
2966 | final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
2967 | final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
2968 | if (llp.isDecor != rlp.isDecor) {
2969 | return llp.isDecor ? 1 : -1;
2970 | }
2971 | return llp.position - rlp.position;
2972 | }
2973 | }
2974 | }
2975 |
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaozhentao/CycleViewPager/73d6f0c50cb88a8adda15dabd7f3a6c2398942cc/screenshot.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------