master 6b219109f1af cached
46 files
174.8 KB
44.8k tokens
189 symbols
1 requests
Download .txt
Repository: NateRobinson/CardStackViewpager
Branch: master
Commit: 6b219109f1af
Files: 46
Total size: 174.8 KB

Directory structure:
gitextract_qfkxk7en/

├── .gitignore
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── gu/
│       │               └── cardstackviewpager/
│       │                   └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── gu/
│       │   │           └── cardstackviewpager/
│       │   │               ├── activity/
│       │   │               │   ├── AboutActivity.java
│       │   │               │   ├── HomeActivity.java
│       │   │               │   └── WebViewActivity.java
│       │   │               ├── adapter/
│       │   │               │   └── ContentFragmentAdapter.java
│       │   │               └── fragment/
│       │   │                   └── CardFragment.java
│       │   └── res/
│       │       ├── anim/
│       │       │   ├── slide_left_in.xml
│       │       │   ├── slide_left_out.xml
│       │       │   ├── slide_right_in.xml
│       │       │   └── slide_right_out.xml
│       │       ├── drawable/
│       │       │   ├── back_iv_bg.xml
│       │       │   ├── custom_ll_bg.xml
│       │       │   ├── github_click_bg.xml
│       │       │   └── progress_bar_h5.xml
│       │       ├── layout/
│       │       │   ├── activity_about.xml
│       │       │   ├── activity_home.xml
│       │       │   ├── activity_webview.xml
│       │       │   └── fragment_card.xml
│       │       └── values/
│       │           ├── colors.xml
│       │           ├── dimens.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── gu/
│                       └── cardstackviewpager/
│                           └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── gu/
│       │               └── library/
│       │                   └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── com/
│       │           └── gu/
│       │               └── library/
│       │                   ├── OrientedViewPager.java
│       │                   ├── transformer/
│       │                   │   ├── VerticalBaseTransformer.java
│       │                   │   └── VerticalStackTransformer.java
│       │                   └── utils/
│       │                       └── ScreenUtils.java
│       └── test/
│           └── java/
│               └── com/
│                   └── gu/
│                       └── library/
│                           └── ExampleUnitTest.java
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
/app/build
.idea


================================================
FILE: README.md
================================================
# CardStackViewpager 卡片翻页效果的Viewpager

这个`CardStackViewpager `的灵感来自Github上面的	[`FlippableStackView`](https://github.com/blipinsk/FlippableStackView)开源项目,而我想实现的效果方向上恰好与`FlippableStackView`相反,并且细节上也有些区别,详见下面的效果对比图:

###### FlippableStackView运行效果图:
![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/two.gif?raw=true)

###### CardStackViewpager运行效果图:
![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/one.gif?raw=true)

这里讲一个小插曲,自己尝试实现`CardStackViewpager`的过程中,由于一开始对`PageTransformer`的`onTransform(View page, float position)`实在很困惑,于是我用自己小学般的英语写了一封邮件给`FlippableStackView`的开发者,尴尬的是,至今他没回我邮件。

回归正题,下面我就来具体讲一下`CardStackViewpager `的实现思路,其实整个核心就在下面这一段代码,把下面这段代码搞懂了,就可以通过自定义自己的`PageTransformer`实现各种各样想要的Viewpager效果了。

#### 核心的VerticalStackTransformer的onTransform方法最终版

```
    @Override
    protected void onTransform(View page, float position) {
        if (position <= 0.0f) {
            page.setAlpha(1.0f);
            Log.e("onTransform", "position <= 0.0f ==>" + position);
            page.setTranslationY(0f);
            //控制停止滑动切换的时候,只有最上面的一张卡片可以点击
            page.setClickable(true);
        } else if (position <= 3.0f) {
            Log.e("onTransform", "position <= 3.0f ==>" + position);
            float scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, spaceBetweenFirAndSecWith * position)) / (float) (page.getWidth());
            //控制下面卡片的可见度
            page.setAlpha(1.0f);
            //控制停止滑动切换的时候,只有最上面的一张卡片可以点击
            page.setClickable(false);
            page.setPivotX(page.getWidth() / 2f);
            page.setPivotY(page.getHeight() / 2f);
            page.setScaleX(scale);
            page.setScaleY(scale);
            page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, spaceBetweenFirAndSecHeight) * position);
        }
    }
```

###### 在分析上面的代码之前,我们需要有以下几个知识准备:
1. Viewpager的`setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer)`方法的第一个参数,用来控制加入到Viewpager的Views对象是正序的还是倒序的,这里为了实现我们想要的效果,需要让第一个添加到布局的View来到第一个展示,所以传入`true`;
2. Viewpager的`setOffscreenPageLimit(int limit)`方法,设置有多少的缓存Views,这个将决定我们的卡片重叠展示的效果显示几层卡片效果。

现在我们继续看上面的`onTransform(View page, float position)`方法,这个方法设计的很巧妙,当初我在探索的时候,通过打印日志来判断这个方法是如何执行的时候,发现这这个`position`的值看似毫无规律,后来我想到以前数学里推理定理时的方法,从`特殊情况入手`,再`一点点分析其他情况`,然后一步步的实现上面的代码。

#### 第一步,分析应用初始化进来的时候的position
此时的`onTransform(View page, float position)`方法如下:

```
    @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform","position  ==>"+position);
        //设置每个卡片y方向偏移量,这样可以使卡片都完全叠加起来
        page.setTranslationY(-page.getHeight() * position);
    }
```

对应日志如下:

![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/three.png?raw=true)

根据这个日志很明显的可以判断得到:由于我现在设置的`setOffscreenPageLimit(int limit)`值为4,所以可以看到position有上面几种情况,显而易见,每个position对应了一张卡片,这个时候界面的效果如图:

![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/five.png?raw=true)

现在猜想2,3,4,5号卡片就在1号卡片下面,现在要想个法子证实我们的猜想,将`onTransform(View page, float position)`方法改成下面这样:

```
    @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform","position  ==>"+position);
        //设置卡片透明度
        page.setAlpha(0.5f);
        //设置缩放中点
        page.setPivotX(page.getWidth() / 2f);
        page.setPivotY(page.getHeight() / 2f);
        //设置缩放的比例 此处设置两个相邻的卡片的缩放比率为0.9f
        page.setScaleX((float) Math.pow(0.9f,position));
        page.setScaleY((float) Math.pow(0.9f,position));
		//设置每个卡片y方向偏移量,这样可以使卡片都完全叠加起来
		page.setTranslationY(-page.getHeight() * position);
    }
```

运行起来之后,证实了我们的想法:

![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/six.png?raw=true)

#### 第二步,实现卡片叠加的最终效果

分析上面的图片效果,可以发现,把第二张卡片往下移动一段距离之后,就可以形成一个卡片叠加的初步效果了,变成下面这样:

![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/seven.png?raw=true)

其他的卡片,道理一样,那么如何实现这个向下偏移的值呢,这个值如何以一个表达式表现出来呢,先看下面的A,B,C步骤的分析图:

![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/eight.png?raw=true)

显而易见,相隔两张卡片的偏移量为:`(H2-H1)+d1`,我们稍微改变一下`onTransform(View page, float position)`方法如下:

```
@Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        page.setAlpha(0.5f);
        page.setPivotX(page.getWidth() / 2f);
        page.setPivotY(page.getHeight() / 2f);
        page.setScaleX((float) Math.pow(0.9f, position));
        page.setScaleY((float) Math.pow(0.9f, position));
        //修改过的代码
        page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - (float) Math.pow(0.9f, position)) + ScreenUtils.dp2px(context, 10));
    }
```

此时的效果图如下:

![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/nine.png?raw=true)

卡片半透明的时候,效果还不是特别的明显,把`page.setAlpha(0.5f)`改为`page.setAlpha(1.0f)`再试一次:

![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/ten.png?raw=true)

惊喜的发现这不就是卡片叠加效果嘛,虽然现在的效果细节还有点问题,我们不急,这个细节问题简单分析一下就会想到,是我们的缩放比例问题导致的,继续下一步的优化,我们将会解决这个问题。

#### 第三步,根据相邻卡片的间距值动态设置缩放值

上面的`onTransform(View page, float position)`方法中,我们的x,y缩放比例都是写的一个固定值`0.9f`,这个显然不能满足日常需求,我这里是设置上下两张卡片的宽度比来作为最终想要的缩放比例,修改`onTransform(View page, float position)`方法如下:

```
    @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        float scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, 20 * position)) / (float) (page.getWidth());
        page.setAlpha(1.0f);
        page.setPivotX(page.getWidth() / 2f);
        page.setPivotY(page.getHeight() / 2f);
        page.setScaleX(scale);
        page.setScaleY(scale);
        //修改过的代码
        page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, 10) * position);
    }
```

再跑一下程序,完美的卡片效果就出现了:

![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/eleven.png?raw=true)

#### 第四步,特殊到一般,实现最终的卡片滑动效果

此时,我们尝试一下滑动Viewpager,发现卡片的切换效果并没有如期的出现,通过多次尝试和分析,我发现,由于我们这里没有对当前滑动过去的那张卡片做特殊处理,这里的特殊处理指的是:为了实现卡片抽动的切换效果,当前滑动的卡片应该不用执行任何缩放和偏移的操作,修改为`page.setTranslationY(0f);`,具体代码如下:

>这里列出一篇博客:	[http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0814/1650.html](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0814/1650.html),他主要讲了对`onTransform(View page, float position)`中position的理解

```
    @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        if (position <= 0.0f) {
            page.setAlpha(1.0f);
            //出现卡片抽动效果的关键代码
            page.setTranslationY(0f);
        } else {
            float scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, 20 * position)) / (float) (page.getWidth());
            page.setAlpha(1.0f);
            page.setPivotX(page.getWidth() / 2f);
            page.setPivotY(page.getHeight() / 2f);
            page.setScaleX(scale);
            page.setScaleY(scale);
            //修改过的代码
            page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, 10) * position);
        }
    }
```

至此,已经可以实现文章开头的动画效果了。回头想一下,我们一直在基于特殊的情况写代码,最后发现其实他就是所有一般情况中的一种,只不过特殊情况由于他的特殊性最容易进行分析总结,更有利于我们编写出易懂的代码。

最后补充下,在实际项目中,在每张卡片上可能还有有点击区域,更可能整张卡片都是一个点击区域,这个时候就会发现一个问题,当处于这种情况的时候:

![enter image description here](https://github.com/NateRobinson/CardStackViewpager/blob/master/img/eleven.png?raw=true)

我不但可以点到卡片1,也会点到卡片2,卡片3。。。这样肯定不行的,所以我们再次回到`onTransform(View page, float position)`方法,在里面加一个控制:

```
   @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        if (position <= 0.0f) {
            //最上面的卡片可以点击
            page.setClickable(true);
            .......
        } else {
            //下面的卡片不可点击
            page.setClickable(false);
            ........
        }
    }
```

另外我们可能只需要4张卡片重叠的效果就行,这个时候改变一下判断条件即可:

```
   @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        if (position <= 0.0f) {
            ......
        //控制显示几张卡片
        } else if(position <= 3.0f) {
		    ......
        }
    }
```

至此这边文章就要结束了,这是我的总结,希望能帮助大家对`onTransform(View page, float position)`方法有一个更深的理解。


================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    buildToolsVersion "28.0.2"

    defaultConfig {
        applicationId "com.gu.cardstackviewpager"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    testImplementation 'junit:junit:4.12'
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation project(':library')
    implementation 'com.android.support:cardview-v7:27.1.1'
}


================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in D:\AndroidTools\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: app/src/androidTest/java/com/gu/cardstackviewpager/ApplicationTest.java
================================================
package com.gu.cardstackviewpager;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}

================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.gu.cardstackviewpager">

  <uses-permission android:name="android.permission.INTERNET"/>

  <application
      android:allowBackup="true"
      android:icon="@mipmap/app_logo"
      android:label="@string/app_name"
      android:theme="@style/AppTheme">
    <!-- 首页-->
    <activity
        android:name=".activity.HomeActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:label="@string/app_name"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="stateAlwaysHidden">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>

    <!-- 关于我的界面-->
    <activity
        android:name=".activity.AboutActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="stateAlwaysHidden"/>

    <!-- webview界面-->
    <activity
        android:name=".activity.WebViewActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="stateAlwaysHidden"/>

    <!--<meta-data-->
        <!--android:name="android.max_aspect"-->
        <!--android:value="2.4" />-->

  </application>

</manifest>

================================================
FILE: app/src/main/java/com/gu/cardstackviewpager/activity/AboutActivity.java
================================================
package com.gu.cardstackviewpager.activity;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import com.gu.cardstackviewpager.R;

/**
 * Created by Nate on 2016/7/22.
 */
public class AboutActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        //设置切换动画
        overridePendingTransition(R.anim.slide_right_in, R.anim.slide_left_out);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_about);


        findViewById(R.id.back_ll).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
        findViewById(R.id.my_blog_ll).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bundle bundle = new Bundle();
                bundle.putString(WebViewActivity.URL_KEY, getString(R.string.my_blog));
                Intent intent = new Intent(AboutActivity.this, WebViewActivity.class);
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });
        findViewById(R.id.my_git_ll).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bundle bundle = new Bundle();
                bundle.putString(WebViewActivity.URL_KEY, getString(R.string.my_github));
                Intent intent = new Intent(AboutActivity.this, WebViewActivity.class);
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });

    }


    @Override
    public void finish() {
        super.finish();
        //设置切换动画
        overridePendingTransition(R.anim.slide_left_in, R.anim.slide_right_out);
    }
}


================================================
FILE: app/src/main/java/com/gu/cardstackviewpager/activity/HomeActivity.java
================================================
package com.gu.cardstackviewpager.activity;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import com.gu.cardstackviewpager.R;
import com.gu.cardstackviewpager.adapter.ContentFragmentAdapter;
import com.gu.cardstackviewpager.fragment.CardFragment;
import com.gu.library.OrientedViewPager;
import com.gu.library.transformer.VerticalStackTransformer;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Nate on 2016/7/22.
 */
public class HomeActivity extends AppCompatActivity {

    private OrientedViewPager mOrientedViewPager;
    private ContentFragmentAdapter mContentFragmentAdapter;
    private List<Fragment> mFragments = new ArrayList<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);

        mOrientedViewPager = (OrientedViewPager) findViewById(R.id.view_pager);

        //制造数据
        for (int i = 0; i < 10; i++) {
            mFragments.add(CardFragment.newInstance(i + 1));
        }

        mContentFragmentAdapter = new
                ContentFragmentAdapter(getSupportFragmentManager(), mFragments);
        //设置viewpager的方向为竖直
        mOrientedViewPager.setOrientation(OrientedViewPager.Orientation.VERTICAL);
        //设置limit
        mOrientedViewPager.setOffscreenPageLimit(4);
        //设置transformer
        mOrientedViewPager.setPageTransformer(true, new VerticalStackTransformer(getApplicationContext()));
        mOrientedViewPager.setAdapter(mContentFragmentAdapter);

        //跳转关于我的界面
        findViewById(R.id.about_iv).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(HomeActivity.this, AboutActivity.class);
                startActivity(intent);
            }
        });
    }
}


================================================
FILE: app/src/main/java/com/gu/cardstackviewpager/activity/WebViewActivity.java
================================================
package com.gu.cardstackviewpager.activity;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.ProgressBar;

import com.gu.cardstackviewpager.R;

/**
 * Created by Nate on 2016/7/22.
 */
public class WebViewActivity extends AppCompatActivity {

    public static final String URL_KEY = "url_key";

    private WebView mWebView;
    private ProgressBar mProgressBar;
    private String url = "https://github.com/NateRobinson";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        //设置切换动画
        overridePendingTransition(R.anim.slide_right_in, R.anim.slide_left_out);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview);

        if (getIntent().getExtras() != null) {
            url = getIntent().getExtras().getString(WebViewActivity.URL_KEY);
        }

        mWebView= (WebView) findViewById(R.id.web_view);
        mProgressBar= (ProgressBar) findViewById(R.id.web_view_progress_bar);

        WebSettings mWebSettings = mWebView.getSettings();
        mWebSettings.setJavaScriptEnabled(true);
        mWebSettings.setUseWideViewPort(true);
        mWebSettings.setSupportZoom(true);
        mWebSettings.setBuiltInZoomControls(true);
        mWebSettings.setDisplayZoomControls(false);
        mWebSettings.setUseWideViewPort(true);
        mWebSettings.setLoadWithOverviewMode(true);
        mWebView.setWebChromeClient(new WebChromeClient());
        mWebView.setWebViewClient(new WebViewClient());

        mWebView.loadUrl(url);
    }

    public class WebChromeClient extends android.webkit.WebChromeClient {
        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            if (newProgress == 100) {
                mProgressBar.setVisibility(View.GONE);
            } else {
                if (mProgressBar.getVisibility() == View.GONE) {
                    mProgressBar.setVisibility(View.VISIBLE);
                }
                mProgressBar.setProgress(newProgress);
            }
            super.onProgressChanged(view, newProgress);
        }
    }

    class WebViewClient extends android.webkit.WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }
    }

    @Override
    public void onBackPressed() {
        if (mWebView.canGoBack()) {
            mWebView.goBack();
        } else {
            super.onBackPressed();
        }
    }


    @Override
    public void finish() {
        super.finish();
        //设置切换动画
        overridePendingTransition(R.anim.slide_left_in, R.anim.slide_right_out);
    }
}


================================================
FILE: app/src/main/java/com/gu/cardstackviewpager/adapter/ContentFragmentAdapter.java
================================================
package com.gu.cardstackviewpager.adapter;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;

import java.util.ArrayList;
import java.util.List;

public class ContentFragmentAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> fragments = new ArrayList<>();
    private int itemPosition= PagerAdapter.POSITION_UNCHANGED;

    public ContentFragmentAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);
        this.fragments = fragments;
    }

    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "";
    }

    @Override
    public int getItemPosition(Object object) {
        return  getItemPosition();
    }

    public int getItemPosition() {
        return itemPosition;
    }
    public void setItemPosition(int itemPosition) {
        this.itemPosition = itemPosition;
    }
}


================================================
FILE: app/src/main/java/com/gu/cardstackviewpager/fragment/CardFragment.java
================================================
package com.gu.cardstackviewpager.fragment;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import com.gu.cardstackviewpager.R;


/**
 * Created by Nate on 2016/7/22.
 */
public class CardFragment extends Fragment {
    private static final String INDEX_KEY = "index_key";

    public static CardFragment newInstance(int index) {
        CardFragment fragment = new CardFragment();
        Bundle bdl = new Bundle();
        bdl.putInt(INDEX_KEY, index);
        fragment.setArguments(bdl);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        final View v = inflater.inflate(R.layout.fragment_card, container, false);
        TextView cardNumTv = (TextView) v.findViewById(R.id.card_num_tv);
        final Bundle bundle = getArguments();
        if (bundle != null) {
            cardNumTv.setText(bundle.getInt(INDEX_KEY, 0) + "");
        }
        cardNumTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(), "点击了" + bundle.getInt(INDEX_KEY, 0) + "", Toast.LENGTH_SHORT).show();
            }
        });
        return v;
    }
}

================================================
FILE: app/src/main/res/anim/slide_left_in.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<set
  xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:duration="300" android:fromXDelta="-100.0%p" android:toXDelta="0.0" />
</set>


================================================
FILE: app/src/main/res/anim/slide_left_out.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<set
  xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:duration="300" android:fromXDelta="0.0" android:toXDelta="-100.0%p" />
</set>


================================================
FILE: app/src/main/res/anim/slide_right_in.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<set
  xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:duration="300" android:fromXDelta="100.0%p" android:toXDelta="0.0" />
</set>


================================================
FILE: app/src/main/res/anim/slide_right_out.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<set
  xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:duration="300" android:fromXDelta="0.0" android:toXDelta="100.0%p" />
</set>


================================================
FILE: app/src/main/res/drawable/back_iv_bg.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@mipmap/back_icon_pressed" android:state_pressed="true"/>
    <item android:drawable="@mipmap/back_icon_normal"/>
</selector>

================================================
FILE: app/src/main/res/drawable/custom_ll_bg.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape>
            <solid android:color="@color/gray_ef"/>
        </shape>
    </item>
    <item >
        <shape>
            <solid android:color="@color/white"/>
        </shape>
    </item>
</selector>

================================================
FILE: app/src/main/res/drawable/github_click_bg.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape>
            <solid android:color="@color/green_dark"/>
        </shape>
    </item>
    <item >
        <shape>
            <solid android:color="@color/green"/>
        </shape>
    </item>
</selector>

================================================
FILE: app/src/main/res/drawable/progress_bar_h5.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@android:id/background"
        android:drawable="@android:color/white"/>
    <item android:id="@android:id/secondaryProgress">
        <scale
            android:drawable="@color/progress_bar_color"
            android:scaleWidth="100%" />
    </item>
    <item android:id="@android:id/progress">
        <scale
            android:drawable="@color/progress_bar_color"
            android:scaleWidth="100%" />
    </item>

</layer-list>

================================================
FILE: app/src/main/res/layout/activity_about.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="@color/white"
              android:orientation="vertical">


    <LinearLayout
        android:id="@+id/back_ll"
        android:layout_width="?attr/actionBarSize"
        android:layout_height="?attr/actionBarSize"
        android:gravity="center">

        <ImageView
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:src="@drawable/back_iv_bg"/>

    </LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="none">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <ImageView
                android:layout_width="120dp"
                android:layout_height="120dp"
                android:layout_gravity="center_horizontal"
                android:src="@mipmap/my_head_icon"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="@dimen/top_margin_big"
                android:drawablePadding="@dimen/normal_padding"
                android:drawableRight="@mipmap/boy_icon"
                android:fontFamily="sans-serif-condensed"
                android:text="@string/my_name"
                android:textColor="@color/text_color"
                android:textSize="@dimen/text_font_size_big_22sp"
                android:textStyle="bold"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/bottom_margin_big"
                android:layout_marginLeft="@dimen/left_margin"
                android:layout_marginRight="@dimen/right_margin"
                android:layout_marginTop="@dimen/top_margin_big"
                android:gravity="center"
                android:text="@string/my_signature"
                android:textColor="@color/text_color"
                android:textIsSelectable="true"
                android:textSize="@dimen/text_font_size_content_14sp"/>

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:layout_marginTop="@dimen/top_margin"
                android:background="@color/gray_ef"/>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:background="@drawable/custom_ll_bg"
                android:gravity="center_vertical"
                >

                <ImageView
                    android:layout_width="28dp"
                    android:layout_height="28dp"
                    android:layout_marginLeft="@dimen/left_margin"
                    android:src="@mipmap/qq_icon"/>

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_marginLeft="@dimen/left_margin_normal"
                    android:layout_weight="1"
                    android:gravity="center_vertical"
                    android:text="@string/my_qq"
                    android:textColor="@color/text_color"
                    android:textIsSelectable="true"
                    android:textSize="@dimen/text_font_size_content_14sp"/>
            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="@color/gray_ef"/>

            <LinearLayout
                android:id="@+id/my_git_ll"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:background="@drawable/custom_ll_bg"
                android:clickable="true"
                android:gravity="center_vertical"
                >

                <ImageView
                    android:layout_width="28dp"
                    android:layout_height="28dp"
                    android:layout_marginLeft="@dimen/left_margin"
                    android:src="@mipmap/github_icon"/>

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_marginLeft="@dimen/left_margin_normal"
                    android:layout_weight="1"
                    android:gravity="center_vertical"
                    android:text="@string/my_github"
                    android:textColor="@color/text_color"
                    android:textSize="@dimen/text_font_size_content_14sp"/>
            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="@color/gray_ef"/>

            <LinearLayout
                android:id="@+id/my_blog_ll"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:background="@drawable/custom_ll_bg"
                android:clickable="true"
                android:gravity="center_vertical"
                >

                <ImageView
                    android:layout_width="28dp"
                    android:layout_height="28dp"
                    android:layout_marginLeft="@dimen/left_margin"
                    android:src="@mipmap/blog_icon"/>

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_marginLeft="@dimen/left_margin_normal"
                    android:layout_weight="1"
                    android:gravity="center_vertical"
                    android:text="@string/my_blog"
                    android:textColor="@color/text_color"
                    android:textSize="@dimen/text_font_size_content_14sp"/>
            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="@color/gray_ef"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="@dimen/top_margin"
                android:text="V 1.0.0"
                android:textColor="@color/text_color"
                android:textSize="@dimen/text_font_size_small_12sp"/>
        </LinearLayout>
    </ScrollView>


</LinearLayout>

================================================
FILE: app/src/main/res/layout/activity_home.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    >

  <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:orientation="horizontal"
      >

    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@color/green"
        android:gravity="center_vertical|left"
        android:paddingLeft="@dimen/left_padding"
        android:text="@string/app_name"
        android:textColor="@color/text_color"
        android:textSize="@dimen/text_font_size_big_22sp"
        />

    <ImageView
        android:id="@+id/about_iv"
        android:layout_width="?attr/actionBarSize"
        android:layout_height="?attr/actionBarSize"
        android:background="@drawable/github_click_bg"
        android:padding="@dimen/normal_padding"
        android:src="@mipmap/about_icon"
        />

  </LinearLayout>


  <com.gu.library.OrientedViewPager
      android:id="@+id/view_pager"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@color/text_color"
      ></com.gu.library.OrientedViewPager>
</LinearLayout>

================================================
FILE: app/src/main/res/layout/activity_webview.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <WebView
            android:id="@+id/web_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </WebView>

        <ProgressBar
            android:id="@+id/web_view_progress_bar"
            style="@style/customProgressBar"
            android:layout_width="match_parent"
            android:layout_height="4dp"
            android:layout_alignParentTop="true"
            android:max="100"/>
    </RelativeLayout>

</LinearLayout>

================================================
FILE: app/src/main/res/layout/fragment_card.xml
================================================
<?xml version="1.0" encoding="utf-8"?><!-- 使用AndroidAutoLayout进行卡片适配 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:card_view="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:focusable="false"
              android:orientation="vertical">

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="@dimen/bottom_margin"
        android:layout_marginLeft="@dimen/left_margin"
        android:layout_marginRight="@dimen/right_margin"
        android:layout_marginTop="@dimen/top_margin"
        android:focusable="false"
        card_view:cardElevation="@dimen/card_elevation">

        <TextView
            android:id="@+id/card_num_tv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textColor="@color/text_color"
            android:textSize="@dimen/text_font_size_big_60sp"
            android:textStyle="bold"/>

    </android.support.v7.widget.CardView>

</LinearLayout>

================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="green">#00BA9C</color>
    <color name="green_dark">#05917a</color>
    <color name="text_color">#141d26</color>
    <color name="transparent">#00000000</color>
    <color name="white">#FFFFFF</color>
    <color name="gray_ef">#efefef</color>
    <color name="progress_bar_color">#00BA9C</color>
</resources>

================================================
FILE: app/src/main/res/values/dimens.xml
================================================
<resources>
    <!-- 字体大小 -->
    <dimen name="text_font_size_big_60sp">60sp</dimen>
    <dimen name="text_font_size_big_22sp">22sp</dimen>
    <dimen name="text_font_size_title_18sp">18sp</dimen>
    <dimen name="text_font_size_title_16sp">16sp</dimen>
    <dimen name="text_font_size_content_14sp">14sp</dimen>
    <dimen name="text_font_size_small_12sp">12sp</dimen>

    <!-- 边距设置 -->
    <dimen name="left_margin">20dp</dimen>
    <dimen name="right_margin">20dp</dimen>
    <dimen name="top_margin">20dp</dimen>
    <dimen name="bottom_margin">40dp</dimen>
    <dimen name="left_padding">8dp</dimen>
    <dimen name="normal_padding">8dp</dimen>
    <dimen name="left_margin_big">10dp</dimen>
    <dimen name="left_margin_normal">8dp</dimen>
    <dimen name="right_margin_big">10dp</dimen>
    <dimen name="right_margin_normal">8dp</dimen>
    <dimen name="top_margin_big">10dp</dimen>
    <dimen name="top_margin_normal">8dp</dimen>
    <dimen name="bottom_margin_big">10dp</dimen>
    <dimen name="bottom_margin_normal">8dp</dimen>



    <!-- cardview阴影-->
    <dimen name="card_elevation">10dp</dimen>
</resources>


================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
  <string name="app_name">CardStackViewpager</string>
  <string name="my_name">Nate Robinson</string>
  <string name="my_qq">840501291</string>
  <string name="my_github">https://github.com/NateRobinson</string>
  <string name="my_blog">http://blog.csdn.net/u011771755</string>
  <string name="my_signature">Never give up~</string>
</resources>


================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>

  <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/green</item>
    <item name="colorPrimaryDark">@color/green_dark</item>
    <item name="colorAccent">@color/green</item>
  </style>


  <style name="customProgressBar" parent="@android:style/Widget.ProgressBar.Horizontal">
    <item name="android:maxHeight">4.0dip</item>
    <item name="android:indeterminateDrawable">@drawable/progress_bar_h5</item>
    <item name="android:progressDrawable">@drawable/progress_bar_h5</item>
    <item name="android:minHeight">4.0dip</item>
  </style>

</resources>


================================================
FILE: app/src/test/java/com/gu/cardstackviewpager/ExampleUnitTest.java
================================================
package com.gu.cardstackviewpager;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * To work on unit tests, switch the Test Artifact in the Build Variants view.
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
  repositories {
    google()
    jcenter()
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:3.2.0'
    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
  }
}

allprojects {
  repositories {
    google()
    jcenter()
    maven { url "https://jitpack.io" }
  }
}

task clean(type: Delete) {
  delete rootProject.buildDir
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

================================================
FILE: gradlew
================================================
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: library/.gitignore
================================================
/build


================================================
FILE: library/build.gradle
================================================
apply plugin: 'com.android.library'

android {
    compileSdkVersion 27
    buildToolsVersion "28.0.2"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    testImplementation 'junit:junit:4.12'
    implementation 'com.android.support:appcompat-v7:27.1.1'
}


================================================
FILE: library/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in D:\AndroidTools\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: library/src/androidTest/java/com/gu/library/ApplicationTest.java
================================================
package com.gu.library;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}

================================================
FILE: library/src/main/AndroidManifest.xml
================================================
<manifest package="com.gu.library">
</manifest>


================================================
FILE: library/src/main/java/com/gu/library/OrientedViewPager.java
================================================
/**
 * Copyright 2015 Bartosz Lipinski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.gu.library;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.FocusFinder;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
import android.widget.Scroller;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * Created by Bartosz Lipinski
 * Based on castorflex's VerticalViewPager (https://github.com/castorflex/VerticalViewPager)
 *
 * 03.05.15
 */
public class OrientedViewPager extends ViewGroup {

  public enum Orientation {
    VERTICAL, HORIZONTAL
  }

  private static final String TAG = "ViewPager";
  private static final boolean DEBUG = false;

  private static final boolean USE_CACHE = false;

  private static final int DEFAULT_OFFSCREEN_PAGES = 1;
  private static final int MAX_SETTLE_DURATION = 600; // ms
  private static final int MIN_DISTANCE_FOR_FLING = 25; // dips

  private static final int DEFAULT_GUTTER_SIZE = 16; // dips

  private static final int MIN_FLING_VELOCITY = 400; // dips

  private static final int[] LAYOUT_ATTRS = new int[] {
      android.R.attr.layout_gravity
  };

  /**
   * Used to track what the expected number of items in the adapter should be.
   * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
   */
  private int mExpectedAdapterCount;

  private static class ItemInfo {
    Object object;
    int position;
    boolean scrolling;
    float sizeFactor;
    float offset;
  }

  private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {
    @Override
    public int compare(ItemInfo lhs, ItemInfo rhs) {
      return lhs.position - rhs.position;
    }
  };

  private static final Interpolator sInterpolator = new Interpolator() {
    public float getInterpolation(float t) {
      t -= 1.0f;
      return t * t * t * t * t + 1.0f;
    }
  };

  private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
  private final ItemInfo mTempItem = new ItemInfo();

  private final Rect mTempRect = new Rect();

  private Orientation mOrientation = Orientation.HORIZONTAL;

  private PagerAdapter mAdapter;
  private int mCurItem;   // Index of currently displayed page.
  private int mRestoredCurItem = -1;
  private Parcelable mRestoredAdapterState = null;
  private ClassLoader mRestoredClassLoader = null;
  private Scroller mScroller;
  private PagerObserver mObserver;

  private int mPageMargin;
  private Drawable mMarginDrawable;
  private int mTopLeftPageBounds;
  private int mBottomRightPageBounds;

  // Offsets of the first and last items, if known.
  // Set during population, used to determine if we are at the beginning
  // or end of the pager data set during touch scrolling.
  private float mFirstOffset = -Float.MAX_VALUE;
  private float mLastOffset = Float.MAX_VALUE;

  private int mChildWidthMeasureSpec;
  private int mChildHeightMeasureSpec;
  private boolean mInLayout;

  private boolean mScrollingCacheEnabled;

  private boolean mPopulatePending;
  private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;

  private boolean mIsBeingDragged;
  private boolean mIsUnableToDrag;
  private boolean mIgnoreGutter;
  private int mDefaultGutterSize;
  private int mGutterSize;
  private int mTouchSlop;
  /**
   * Position of the last motion event.
   */
  private float mLastMotionX;
  private float mLastMotionY;
  private float mInitialMotionX;
  private float mInitialMotionY;
  /**
   * ID of the active pointer. This is used to retain consistency during
   * drags/flings if multiple pointers are used.
   */
  private int mActivePointerId = INVALID_POINTER;
  /**
   * Sentinel value for no current active pointer.
   * Used by {@link #mActivePointerId}.
   */
  private static final int INVALID_POINTER = -1;

  /**
   * Determines speed during touch scrolling
   */
  private VelocityTracker mVelocityTracker;
  private int mMinimumVelocity;
  private int mMaximumVelocity;
  private int mFlingDistance;
  private int mCloseEnough;

  // If the pager is at least this close to its final position, complete the scroll
  // on touch down and let the user interact with the content inside instead of
  // "catching" the flinging pager.
  private static final int CLOSE_ENOUGH = 2; // dp

  private boolean mFakeDragging;
  private long mFakeDragBeginTime;

  private EdgeEffectCompat mTopLeftEdge;
  private EdgeEffectCompat mRightBottomEdge;

  private boolean mFirstLayout = true;
  private boolean mNeedCalculatePageOffsets = false;
  private boolean mCalledSuper;
  private int mDecorChildCount;

  private ViewPager.OnPageChangeListener mOnPageChangeListener;
  private ViewPager.OnPageChangeListener mInternalPageChangeListener;
  private OnAdapterChangeListener mAdapterChangeListener;
  private ViewPager.PageTransformer mPageTransformer;
  private Method mSetChildrenDrawingOrderEnabled;

  private static final int DRAW_ORDER_DEFAULT = 0;
  private static final int DRAW_ORDER_FORWARD = 1;
  private static final int DRAW_ORDER_REVERSE = 2;
  private int mDrawingOrder;
  private ArrayList<View> mDrawingOrderedChildren;
  private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();

  /**
   * Indicates that the pager is in an idle, settled state. The current page
   * is fully in view and no animation is in progress.
   */
  public static final int SCROLL_STATE_IDLE = 0;

  /**
   * Indicates that the pager is currently being dragged by the user.
   */
  public static final int SCROLL_STATE_DRAGGING = 1;

  /**
   * Indicates that the pager is in the process of settling to a final position.
   */
  public static final int SCROLL_STATE_SETTLING = 2;

  private final Runnable mEndScrollRunnable = new Runnable() {
    public void run() {
      setScrollState(SCROLL_STATE_IDLE);
      populate();
    }
  };

  private int mScrollState = SCROLL_STATE_IDLE;

  /**
   * Used internally to monitor when adapters are switched.
   */
  interface OnAdapterChangeListener {
    public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
  }

  /**
   * Used internally to tag special types of child views that should be added as
   * pager decorations by default.
   */
  interface Decor {
  }

  public OrientedViewPager(Context context) {
    super(context);
    initViewPager();
  }

  public OrientedViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    initViewPager();
  }

  void initViewPager() {
    setWillNotDraw(false);
    setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    setFocusable(true);
    final Context context = getContext();
    mScroller = new Scroller(context, sInterpolator);
    final ViewConfiguration configuration = ViewConfiguration.get(context);
    final float density = context.getResources().getDisplayMetrics().density;

    mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    mTopLeftEdge = new EdgeEffectCompat(context);
    mRightBottomEdge = new EdgeEffectCompat(context);

    mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
    mCloseEnough = (int) (CLOSE_ENOUGH * density);
    mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);

    ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());

    if (ViewCompat.getImportantForAccessibility(this)
        == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
      ViewCompat.setImportantForAccessibility(this,
          ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    }
  }

  public void setOrientation(Orientation orientation) {
    mOrientation = orientation;
  }

  @Override
  protected void onDetachedFromWindow() {
    removeCallbacks(mEndScrollRunnable);
    super.onDetachedFromWindow();
  }

  private void setScrollState(int newState) {
    if (mScrollState == newState) {
      return;
    }

    mScrollState = newState;
    if (mPageTransformer != null) {
      // PageTransformers can do complex things that benefit from hardware layers.
      enableLayers(newState != SCROLL_STATE_IDLE);
    }
    if (mOnPageChangeListener != null) {
      mOnPageChangeListener.onPageScrollStateChanged(newState);
    }
  }

  /**
   * Set a PagerAdapter that will supply views for this pager as needed.
   *
   * @param adapter Adapter to use
   */
  public void setAdapter(PagerAdapter adapter) {
    if (mAdapter != null) {
      mAdapter.unregisterDataSetObserver(mObserver);
      mAdapter.startUpdate(this);
      for (int i = 0; i < mItems.size(); i++) {
        final ItemInfo ii = mItems.get(i);
        mAdapter.destroyItem(this, ii.position, ii.object);
      }
      mAdapter.finishUpdate(this);
      mItems.clear();
      removeNonDecorViews();
      mCurItem = 0;
      scrollTo(0, 0);
    }

    final PagerAdapter oldAdapter = mAdapter;
    mAdapter = adapter;
    mExpectedAdapterCount = 0;

    if (mAdapter != null) {
      if (mObserver == null) {
        mObserver = new PagerObserver();
      }
      mAdapter.registerDataSetObserver(mObserver);
      mPopulatePending = false;
      final boolean wasFirstLayout = mFirstLayout;
      mFirstLayout = true;
      mExpectedAdapterCount = mAdapter.getCount();
      if (mRestoredCurItem >= 0) {
        mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
        setCurrentItemInternal(mRestoredCurItem, false, true);
        mRestoredCurItem = -1;
        mRestoredAdapterState = null;
        mRestoredClassLoader = null;
      } else if (!wasFirstLayout) {
        populate();
      } else {
        requestLayout();
      }
    }

    if (mAdapterChangeListener != null && oldAdapter != adapter) {
      mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
    }
  }

  private void removeNonDecorViews() {
    for (int i = 0; i < getChildCount(); i++) {
      final View child = getChildAt(i);
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
      if (!lp.isDecor) {
        removeViewAt(i);
        i--;
      }
    }
  }

  /**
   * Retrieve the current adapter supplying pages.
   *
   * @return The currently registered PagerAdapter
   */
  public PagerAdapter getAdapter() {
    return mAdapter;
  }

  void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
    mAdapterChangeListener = listener;
  }

  private int getClientSize() {
    return (mOrientation == Orientation.VERTICAL) ?
        getMeasuredHeight() - getPaddingTop() - getPaddingBottom() :
        getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
  }

  /**
   * Set the currently selected page. If the ViewPager has already been through its first
   * layout with its current adapter there will be a smooth animated transition between
   * the current item and the specified item.
   *
   * @param item Item index to select
   */
  public void setCurrentItem(int item) {
    mPopulatePending = false;
    setCurrentItemInternal(item, !mFirstLayout, false);
  }

  /**
   * Set the currently selected page.
   *
   * @param item Item index to select
   * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
   */
  public void setCurrentItem(int item, boolean smoothScroll) {
    mPopulatePending = false;
    setCurrentItemInternal(item, smoothScroll, false);
  }

  public int getCurrentItem() {
    return mCurItem;
  }

  void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
    setCurrentItemInternal(item, smoothScroll, always, 0);
  }

  void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
    if (mAdapter == null || mAdapter.getCount() <= 0) {
      setScrollingCacheEnabled(false);
      return;
    }
    if (!always && mCurItem == item && mItems.size() != 0) {
      setScrollingCacheEnabled(false);
      return;
    }

    if (item < 0) {
      item = 0;
    } else if (item >= mAdapter.getCount()) {
      item = mAdapter.getCount() - 1;
    }
    final int pageLimit = mOffscreenPageLimit;
    if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
      // We are doing a jump by more than one page.  To avoid
      // glitches, we want to keep all current pages in the view
      // until the scroll ends.
      for (int i = 0; i < mItems.size(); i++) {
        mItems.get(i).scrolling = true;
      }
    }
    final boolean dispatchSelected = mCurItem != item;

    if (mFirstLayout) {
      // We don't have any idea how big we are yet and shouldn't have any pages either.
      // Just set things up and let the pending layout handle things.
      mCurItem = item;
      if (dispatchSelected && mOnPageChangeListener != null) {
        mOnPageChangeListener.onPageSelected(item);
      }
      if (dispatchSelected && mInternalPageChangeListener != null) {
        mInternalPageChangeListener.onPageSelected(item);
      }
      requestLayout();
    } else {
      populate(item);
      scrollToItem(item, smoothScroll, velocity, dispatchSelected);
    }
  }

  private void scrollToItem(int item, boolean smoothScroll, int velocity,
      boolean dispatchSelected) {
    final ItemInfo curInfo = infoForPosition(item);
    int dest = 0;
    if (curInfo != null) {
      final int size = getClientSize();
      dest = (int) (size * Math.max(mFirstOffset,
          Math.min(curInfo.offset, mLastOffset)));
    }
    if (smoothScroll) {
      if (mOrientation == Orientation.VERTICAL) {
        smoothScrollTo(0, dest, velocity);
      } else {
        smoothScrollTo(dest, 0, velocity);
      }
      if (dispatchSelected && mOnPageChangeListener != null) {
        mOnPageChangeListener.onPageSelected(item);
      }
      if (dispatchSelected && mInternalPageChangeListener != null) {
        mInternalPageChangeListener.onPageSelected(item);
      }
    } else {
      if (dispatchSelected && mOnPageChangeListener != null) {
        mOnPageChangeListener.onPageSelected(item);
      }
      if (dispatchSelected && mInternalPageChangeListener != null) {
        mInternalPageChangeListener.onPageSelected(item);
      }
      completeScroll(false);
      if (mOrientation == Orientation.VERTICAL) {
        scrollTo(0, dest);
      } else {
        scrollTo(dest, 0);
      }
      pageScrolled(dest);
    }
  }

  /**
   * Set a listener that will be invoked whenever the page changes or is incrementally
   * scrolled. See {@link android.support.v4.view.ViewPager.OnPageChangeListener}.
   *
   * @param listener Listener to set
   */
  public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
    mOnPageChangeListener = listener;
  }

  /**
   * Set a {@link android.support.v4.view.ViewPager.PageTransformer} that will be called for each
   * attached page whenever
   * the scroll position is changed. This allows the application to apply custom property
   * transformations to each page, overriding the default sliding look and feel.
   * <p/>
   * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
   * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
   *
   * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
   * to be drawn from last to first instead of first to last.
   * @param transformer PageTransformer that will modify each page's animation properties
   */
  public void setPageTransformer(boolean reverseDrawingOrder,
      ViewPager.PageTransformer transformer) {
    if (Build.VERSION.SDK_INT >= 11) {
      final boolean hasTransformer = transformer != null;
      final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
      mPageTransformer = transformer;
      setChildrenDrawingOrderEnabledCompat(hasTransformer);
      if (hasTransformer) {
        mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
      } else {
        mDrawingOrder = DRAW_ORDER_DEFAULT;
      }
      if (needsPopulate) populate();
    }
  }

  void setChildrenDrawingOrderEnabledCompat(boolean enable) {
    if (Build.VERSION.SDK_INT >= 7) {
      if (mSetChildrenDrawingOrderEnabled == null) {
        try {
          mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(
              "setChildrenDrawingOrderEnabled", new Class[] { Boolean.TYPE });
        } catch (NoSuchMethodException e) {
          Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
        }
      }
      try {
        mSetChildrenDrawingOrderEnabled.invoke(this, enable);
      } catch (Exception e) {
        Log.e(TAG, "Error changing children drawing order", e);
      }
    }
  }

  @Override
  protected int getChildDrawingOrder(int childCount, int i) {
    final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
    final int result =
        ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
    return result;
  }

  /**
   * Set a separate OnPageChangeListener for internal use by the support library.
   *
   * @param listener Listener to set
   * @return The old listener that was set, if any.
   */
  ViewPager.OnPageChangeListener setInternalPageChangeListener(
      ViewPager.OnPageChangeListener listener) {
    ViewPager.OnPageChangeListener oldListener = mInternalPageChangeListener;
    mInternalPageChangeListener = listener;
    return oldListener;
  }

  /**
   * Returns the number of pages that will be retained to either side of the
   * current page in the view hierarchy in an idle state. Defaults to 1.
   *
   * @return How many pages will be kept offscreen on either side
   * @see #setOffscreenPageLimit(int)
   */
  public int getOffscreenPageLimit() {
    return mOffscreenPageLimit;
  }

  /**
   * Set the number of pages that should be retained to either side of the
   * current page in the view hierarchy in an idle state. Pages beyond this
   * limit will be recreated from the adapter when needed.
   * <p/>
   * <p>This is offered as an optimization. If you know in advance the number
   * of pages you will need to support or have lazy-loading mechanisms in place
   * on your pages, tweaking this setting can have benefits in perceived smoothness
   * of paging animations and interaction. If you have a small number of pages (3-4)
   * that you can keep active all at once, less time will be spent in layout for
   * newly created view subtrees as the user pages back and forth.</p>
   * <p/>
   * <p>You should keep this limit low, especially if your pages have complex layouts.
   * This setting defaults to 1.</p>
   *
   * @param limit How many pages will be kept offscreen in an idle state.
   */
  public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
      Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
          DEFAULT_OFFSCREEN_PAGES);
      limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
      mOffscreenPageLimit = limit;
      populate();
    }
  }

  /**
   * Set the margin between pages.
   *
   * @param marginPixels Distance between adjacent pages in pixels
   * @see #getPageMargin()
   * @see #setPageMarginDrawable(Drawable)
   * @see #setPageMarginDrawable(int)
   */
  public void setPageMargin(int marginPixels) {
    final int oldMargin = mPageMargin;
    mPageMargin = marginPixels;

    final int size = (mOrientation == Orientation.VERTICAL) ? getHeight() : getWidth();
    recomputeScrollPosition(size, size, marginPixels, oldMargin);

    requestLayout();
  }

  /**
   * Return the margin between pages.
   *
   * @return The size of the margin in pixels
   */
  public int getPageMargin() {
    return mPageMargin;
  }

  /**
   * Set a drawable that will be used to fill the margin between pages.
   *
   * @param d Drawable to display between pages
   */
  public void setPageMarginDrawable(Drawable d) {
    mMarginDrawable = d;
    if (d != null) refreshDrawableState();
    setWillNotDraw(d == null);
    invalidate();
  }

  /**
   * Set a drawable that will be used to fill the margin between pages.
   *
   * @param resId Resource ID of a drawable to display between pages
   */
  public void setPageMarginDrawable(int resId) {
    setPageMarginDrawable(getContext().getResources().getDrawable(resId));
  }

  @Override
  protected boolean verifyDrawable(Drawable who) {
    return super.verifyDrawable(who) || who == mMarginDrawable;
  }

  @Override
  protected void drawableStateChanged() {
    super.drawableStateChanged();
    final Drawable d = mMarginDrawable;
    if (d != null && d.isStateful()) {
      d.setState(getDrawableState());
    }
  }

  // We want the duration of the page snap animation to be influenced by the distance that
  // the screen has to travel, however, we don't want this duration to be effected in a
  // purely linear fashion. Instead, we use this method to moderate the effect that the distance
  // of travel has on the overall snap duration.
  float distanceInfluenceForSnapDuration(float f) {
    f -= 0.5f; // center the values about 0.
    f *= 0.3f * Math.PI / 2.0f;
    return (float) Math.sin(f);
  }

  /**
   * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
   *
   * @param x the number of pixels to scroll by on the X axis
   * @param y the number of pixels to scroll by on the Y axis
   */
  void smoothScrollTo(int x, int y) {
    smoothScrollTo(x, y, 0);
  }

  /**
   * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
   *
   * @param x the number of pixels to scroll by on the X axis
   * @param y the number of pixels to scroll by on the Y axis
   * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
   */
  void smoothScrollTo(int x, int y, int velocity) {
    if (getChildCount() == 0) {
      // Nothing to do.
      setScrollingCacheEnabled(false);
      return;
    }
    int sx = getScrollX();
    int sy = getScrollY();
    int dx = x - sx;
    int dy = y - sy;
    if (dx == 0 && dy == 0) {
      completeScroll(false);
      populate();
      setScrollState(SCROLL_STATE_IDLE);
      return;
    }

    setScrollingCacheEnabled(true);
    setScrollState(SCROLL_STATE_SETTLING);

    final int size = getClientSize();
    final int halfSize = size / 2;
    final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / size);
    final float distance = halfSize + halfSize *
        distanceInfluenceForSnapDuration(distanceRatio);

    int duration = 0;
    velocity = Math.abs(velocity);
    if (velocity > 0) {
      duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
    } else {
      final float pageSize = size * mAdapter.getPageWidth(mCurItem);
      final float pageDelta = (float) Math.abs(dx) / (pageSize + mPageMargin);
      duration = (int) ((pageDelta + 1) * 100);
    }
    duration = Math.min(duration, MAX_SETTLE_DURATION);

    mScroller.startScroll(sx, sy, dx, dy, duration);
    ViewCompat.postInvalidateOnAnimation(this);
  }

  ItemInfo addNewItem(int position, int index) {
    ItemInfo ii = new ItemInfo();
    ii.position = position;
    ii.object = mAdapter.instantiateItem(this, position);
    ii.sizeFactor = mAdapter.getPageWidth(position);
    if (index < 0 || index >= mItems.size()) {
      mItems.add(ii);
    } else {
      mItems.add(index, ii);
    }
    return ii;
  }

  void dataSetChanged() {
    // This method only gets called if our observer is attached, so mAdapter is non-null.

    final int adapterCount = mAdapter.getCount();
    mExpectedAdapterCount = adapterCount;
    boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
        mItems.size() < adapterCount;
    int newCurrItem = mCurItem;

    boolean isUpdating = false;
    for (int i = 0; i < mItems.size(); i++) {
      final ItemInfo ii = mItems.get(i);
      final int newPos = mAdapter.getItemPosition(ii.object);

      if (newPos == PagerAdapter.POSITION_UNCHANGED) {
        continue;
      }

      if (newPos == PagerAdapter.POSITION_NONE) {
        mItems.remove(i);
        i--;

        if (!isUpdating) {
          mAdapter.startUpdate(this);
          isUpdating = true;
        }

        mAdapter.destroyItem(this, ii.position, ii.object);
        needPopulate = true;

        if (mCurItem == ii.position) {
          // Keep the current item in the valid range
          newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
          needPopulate = true;
        }
        continue;
      }

      if (ii.position != newPos) {
        if (ii.position == mCurItem) {
          // Our current item changed position. Follow it.
          newCurrItem = newPos;
        }

        ii.position = newPos;
        needPopulate = true;
      }
    }

    if (isUpdating) {
      mAdapter.finishUpdate(this);
    }

    Collections.sort(mItems, COMPARATOR);

    if (needPopulate) {
      // Reset our known page widths; populate will recompute them.
      final int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.isDecor) {
          lp.heightFactor = 0.f;
        }
      }

      setCurrentItemInternal(newCurrItem, false, true);
      requestLayout();
    }
  }

  void populate() {
    populate(mCurItem);
  }

  void populate(int newCurrentItem) {
    ItemInfo oldCurInfo = null;
    int focusDirection = View.FOCUS_FORWARD;
    if (mCurItem != newCurrentItem) {
      focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;
      oldCurInfo = infoForPosition(mCurItem);
      mCurItem = newCurrentItem;
    }

    if (mAdapter == null) {
      sortChildDrawingOrder();
      return;
    }

    // Bail now if we are waiting to populate.  This is to hold off
    // on creating views from the time the user releases their finger to
    // fling to a new position until we have finished the scroll to
    // that position, avoiding glitches from happening at that point.
    if (mPopulatePending) {
      if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
      sortChildDrawingOrder();
      return;
    }

    // Also, don't populate until we are attached to a window.  This is to
    // avoid trying to populate before we have restored our view hierarchy
    // state and conflicting with what is restored.
    if (getWindowToken() == null) {
      return;
    }

    mAdapter.startUpdate(this);

    final int pageLimit = mOffscreenPageLimit;
    final int startPos = Math.max(0, mCurItem - pageLimit);
    final int N = mAdapter.getCount();
    final int endPos = Math.min(N - 1, mCurItem + pageLimit);

    if (N != mExpectedAdapterCount) {
      String resName;
      try {
        resName = getResources().getResourceName(getId());
      } catch (Resources.NotFoundException e) {
        resName = Integer.toHexString(getId());
      }
      throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
          " contents without calling PagerAdapter#notifyDataSetChanged!" +
          " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
          " Pager id: " + resName +
          " Pager class: " + getClass() +
          " Problematic adapter: " + mAdapter.getClass());
    }

    // Locate the currently focused item or add it if needed.
    int curIndex = -1;
    ItemInfo curItem = null;
    for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
      final ItemInfo ii = mItems.get(curIndex);
      if (ii.position >= mCurItem) {
        if (ii.position == mCurItem) curItem = ii;
        break;
      }
    }

    if (curItem == null && N > 0) {
      curItem = addNewItem(mCurItem, curIndex);
    }

    // Fill 3x the available width or up to the number of offscreen
    // pages requested to either side, whichever is larger.
    // If we have no current item we have no work to do.
    if (curItem != null) {
      float extraSizeTopLeft = 0.f;
      int itemIndex = curIndex - 1;
      ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
      final int clientSize = getClientSize();
      final float topLeftSizeNeeded = clientSize <= 0 ? 0 :
          2.f - curItem.sizeFactor + (float) getPaddingLeft() / (float) clientSize;
      for (int pos = mCurItem - 1; pos >= 0; pos--) {
        if (extraSizeTopLeft >= topLeftSizeNeeded && pos < startPos) {
          if (ii == null) {
            break;
          }
          if (pos == ii.position && !ii.scrolling) {
            mItems.remove(itemIndex);
            mAdapter.destroyItem(this, pos, ii.object);
            if (DEBUG) {
              Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
                  " view: " + ((View) ii.object));
            }
            itemIndex--;
            curIndex--;
            ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
          }
        } else if (ii != null && pos == ii.position) {
          extraSizeTopLeft += ii.sizeFactor;
          itemIndex--;
          ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
        } else {
          ii = addNewItem(pos, itemIndex + 1);
          extraSizeTopLeft += ii.sizeFactor;
          curIndex++;
          ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
        }
      }

      float extraSizeBottomRight = curItem.sizeFactor;
      itemIndex = curIndex + 1;
      if (extraSizeBottomRight < 2.f) {
        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
        final float bottomRightSizeNeeded = clientSize <= 0 ? 0 :
            (float) getPaddingRight() / (float) clientSize + 2.f;
        for (int pos = mCurItem + 1; pos < N; pos++) {
          if (extraSizeBottomRight >= bottomRightSizeNeeded && pos > endPos) {
            if (ii == null) {
              break;
            }
            if (pos == ii.position && !ii.scrolling) {
              mItems.remove(itemIndex);
              mAdapter.destroyItem(this, pos, ii.object);
              if (DEBUG) {
                Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
                    " view: " + ((View) ii.object));
              }
              ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
            }
          } else if (ii != null && pos == ii.position) {
            extraSizeBottomRight += ii.sizeFactor;
            itemIndex++;
            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
          } else {
            ii = addNewItem(pos, itemIndex);
            itemIndex++;
            extraSizeBottomRight += ii.sizeFactor;
            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
          }
        }
      }

      calculatePageOffsets(curItem, curIndex, oldCurInfo);
    }

    if (DEBUG) {
      Log.i(TAG, "Current page list:");
      for (int i = 0; i < mItems.size(); i++) {
        Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
      }
    }

    mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);

    mAdapter.finishUpdate(this);

    // Check width measurement of current pages and drawing sort order.
    // Update LayoutParams as needed.
    final int childCount = getChildCount();
    if (mOrientation == Orientation.VERTICAL) {
      for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        lp.childIndex = i;
        if (!lp.isDecor && lp.heightFactor == 0.f) {
          // 0 means requery the adapter for this, it doesn't have a valid width
          // .
          final ItemInfo ii = infoForChild(child);
          if (ii != null) {
            lp.heightFactor = ii.sizeFactor;
            lp.position = ii.position;
          }
        }
      }
    } else {
      for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        lp.childIndex = i;
        if (!lp.isDecor && lp.widthFactor == 0.f) {
          // 0 means requery the adapter for this, it doesn't have a valid width.
          final ItemInfo ii = infoForChild(child);
          if (ii != null) {
            lp.widthFactor = ii.sizeFactor;
            lp.position = ii.position;
          }
        }
      }
    }
    sortChildDrawingOrder();

    if (hasFocus()) {
      View currentFocused = findFocus();
      ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
      if (ii == null || ii.position != mCurItem) {
        for (int i = 0; i < getChildCount(); i++) {
          View child = getChildAt(i);
          ii = infoForChild(child);
          if (ii != null && ii.position == mCurItem) {
            if (child.requestFocus(focusDirection)) {
              break;
            }
          }
        }
      }
    }
  }

  private void sortChildDrawingOrder() {
    if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
      if (mDrawingOrderedChildren == null) {
        mDrawingOrderedChildren = new ArrayList<View>();
      } else {
        mDrawingOrderedChildren.clear();
      }
      final int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        mDrawingOrderedChildren.add(child);
      }
      Collections.sort(mDrawingOrderedChildren, sPositionComparator);
    }
  }

  private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
    final int N = mAdapter.getCount();
    final int size = getClientSize();
    final float marginOffset = size > 0 ? (float) mPageMargin / size : 0;
    // Fix up offsets for later layout.
    if (oldCurInfo != null) {
      final int oldCurPosition = oldCurInfo.position;
      // Base offsets off of oldCurInfo.
      if (oldCurPosition < curItem.position) {
        int itemIndex = 0;
        ItemInfo ii = null;
        float offset = oldCurInfo.offset + oldCurInfo.sizeFactor + marginOffset;
        for (int pos = oldCurPosition + 1;
            pos <= curItem.position && itemIndex < mItems.size(); pos++) {
          ii = mItems.get(itemIndex);
          while (pos > ii.position && itemIndex < mItems.size() - 1) {
            itemIndex++;
            ii = mItems.get(itemIndex);
          }
          while (pos < ii.position) {
            // We don't have an item populated for this,
            // ask the adapter for an offset.
            offset += mAdapter.getPageWidth(pos) + marginOffset;
            pos++;
          }
          ii.offset = offset;
          offset += ii.sizeFactor + marginOffset;
        }
      } else if (oldCurPosition > curItem.position) {
        int itemIndex = mItems.size() - 1;
        ItemInfo ii = null;
        float offset = oldCurInfo.offset;
        for (int pos = oldCurPosition - 1;
            pos >= curItem.position && itemIndex >= 0; pos--) {
          ii = mItems.get(itemIndex);
          while (pos < ii.position && itemIndex > 0) {
            itemIndex--;
            ii = mItems.get(itemIndex);
          }
          while (pos > ii.position) {
            // We don't have an item populated for this,
            // ask the adapter for an offset.
            offset -= mAdapter.getPageWidth(pos) + marginOffset;
            pos--;
          }
          offset -= ii.sizeFactor + marginOffset;
          ii.offset = offset;
        }
      }
    }

    // Base all offsets off of curItem.
    final int itemCount = mItems.size();
    float offset = curItem.offset;
    int pos = curItem.position - 1;
    mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
    mLastOffset = curItem.position == N - 1 ?
        curItem.offset + curItem.sizeFactor - 1 : Float.MAX_VALUE;
    // Previous pages
    for (int i = curIndex - 1; i >= 0; i--, pos--) {
      final ItemInfo ii = mItems.get(i);
      while (pos > ii.position) {
        offset -= mAdapter.getPageWidth(pos--) + marginOffset;
      }
      offset -= ii.sizeFactor + marginOffset;
      ii.offset = offset;
      if (ii.position == 0) mFirstOffset = offset;
    }
    offset = curItem.offset + curItem.sizeFactor + marginOffset;
    pos = curItem.position + 1;
    // Next pages
    for (int i = curIndex + 1; i < itemCount; i++, pos++) {
      final ItemInfo ii = mItems.get(i);
      while (pos < ii.position) {
        offset += mAdapter.getPageWidth(pos++) + marginOffset;
      }
      if (ii.position == N - 1) {
        mLastOffset = offset + ii.sizeFactor - 1;
      }
      ii.offset = offset;
      offset += ii.sizeFactor + marginOffset;
    }

    mNeedCalculatePageOffsets = false;
  }

  /**
   * This is the persistent state that is saved by ViewPager.  Only needed
   * if you are creating a sublass of ViewPager that must save its own
   * state, in which case it should implement a subclass of this which
   * contains that state.
   */
  public static class ViewPagerSavedState extends BaseSavedState {
    int position;
    Parcelable adapterState;
    ClassLoader loader;

    public ViewPagerSavedState(Parcelable superState) {
      super(superState);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
      super.writeToParcel(out, flags);
      out.writeInt(position);
      out.writeParcelable(adapterState, flags);
    }

    @Override
    public String toString() {
      return "FragmentPager.SavedState{"
          + Integer.toHexString(System.identityHashCode(this))
          + " position=" + position + "}";
    }

    public static final Parcelable.Creator<ViewPagerSavedState> CREATOR
        = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<ViewPagerSavedState>() {
      @Override
      public ViewPagerSavedState createFromParcel(Parcel in, ClassLoader loader) {
        return new ViewPagerSavedState(in, loader);
      }

      @Override
      public ViewPagerSavedState[] newArray(int size) {
        return new ViewPagerSavedState[size];
      }
    });

    ViewPagerSavedState(Parcel in, ClassLoader loader) {
      super(in);
      if (loader == null) {
        loader = getClass().getClassLoader();
      }
      position = in.readInt();
      adapterState = in.readParcelable(loader);
      this.loader = loader;
    }
  }

  @Override
  public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    ViewPagerSavedState ss = new ViewPagerSavedState(superState);
    ss.position = mCurItem;
    if (mAdapter != null) {
      ss.adapterState = mAdapter.saveState();
    }
    return ss;
  }

  @Override
  public void onRestoreInstanceState(Parcelable state) {
    if (!(state instanceof ViewPagerSavedState)) {
      super.onRestoreInstanceState(state);
      return;
    }

    ViewPagerSavedState ss = (ViewPagerSavedState) state;
    super.onRestoreInstanceState(ss.getSuperState());

    if (mAdapter != null) {
      mAdapter.restoreState(ss.adapterState, ss.loader);
      setCurrentItemInternal(ss.position, false, true);
    } else {
      mRestoredCurItem = ss.position;
      mRestoredAdapterState = ss.adapterState;
      mRestoredClassLoader = ss.loader;
    }
  }

  @Override
  public void addView(View child, int index, ViewGroup.LayoutParams params) {
    if (!checkLayoutParams(params)) {
      params = generateLayoutParams(params);
    }
    final LayoutParams lp = (LayoutParams) params;
    lp.isDecor |= child instanceof Decor;
    if (mInLayout) {
      if (lp != null && lp.isDecor) {
        throw new IllegalStateException("Cannot add pager decor view during layout");
      }
      lp.needsMeasure = true;
      addViewInLayout(child, index, params);
    } else {
      super.addView(child, index, params);
    }

    if (USE_CACHE) {
      if (child.getVisibility() != GONE) {
        child.setDrawingCacheEnabled(mScrollingCacheEnabled);
      } else {
        child.setDrawingCacheEnabled(false);
      }
    }
  }

  @Override
  public void removeView(View view) {
    if (mInLayout) {
      removeViewInLayout(view);
    } else {
      super.removeView(view);
    }
  }

  ItemInfo infoForChild(View child) {
    for (int i = 0; i < mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      if (mAdapter.isViewFromObject(child, ii.object)) {
        return ii;
      }
    }
    return null;
  }

  ItemInfo infoForAnyChild(View child) {
    ViewParent parent;
    while ((parent = child.getParent()) != this) {
      if (parent == null || !(parent instanceof View)) {
        return null;
      }
      child = (View) parent;
    }
    return infoForChild(child);
  }

  ItemInfo infoForPosition(int position) {
    for (int i = 0; i < mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      if (ii.position == position) {
        return ii;
      }
    }
    return null;
  }

  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    mFirstLayout = true;
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // For simple implementation, our internal size is always 0.
    // We depend on the container to specify the layout size of
    // our view.  We can't really know what it is since we will be
    // adding and removing different arbitrary views and do not
    // want the layout to change as this happens.
    setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
        getDefaultSize(0, heightMeasureSpec));

    final int measuredSize =
        (mOrientation == Orientation.VERTICAL) ? getMeasuredHeight() : getMeasuredWidth();
    final int maxGutterSize = measuredSize / 10;
    mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);

    // Children are just made to fill our space.
    int childWidthSize;
    int childHeightSize;

    if (mOrientation == Orientation.VERTICAL) {
      childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
      childHeightSize = measuredSize - getPaddingTop() - getPaddingBottom();
    } else {
      childWidthSize = measuredSize - getPaddingLeft() - getPaddingRight();
      childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
    }

    /*
     * Make sure all children have been properly measured. Decor views first.
     * Right now we cheat and make this less complicated by assuming decor
     * views won't intersect. We will pin to edges based on gravity.
     */
    int size = getChildCount();
    for (int i = 0; i < size; ++i) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (lp != null && lp.isDecor) {
          final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
          final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
          int widthMode = MeasureSpec.AT_MOST;
          int heightMode = MeasureSpec.AT_MOST;
          boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
          boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;

          if (consumeVertical) {
            widthMode = MeasureSpec.EXACTLY;
          } else if (consumeHorizontal) {
            heightMode = MeasureSpec.EXACTLY;
          }

          int widthSize = childWidthSize;
          int heightSize = childHeightSize;
          if (lp.width != LayoutParams.WRAP_CONTENT) {
            widthMode = MeasureSpec.EXACTLY;
            if (lp.width != LayoutParams.FILL_PARENT) {
              widthSize = lp.width;
            }
          }
          if (lp.height != LayoutParams.WRAP_CONTENT) {
            heightMode = MeasureSpec.EXACTLY;
            if (lp.height != LayoutParams.FILL_PARENT) {
              heightSize = lp.height;
            }
          }
          final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
          final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
          child.measure(widthSpec, heightSpec);

          if (consumeVertical) {
            childHeightSize -= child.getMeasuredHeight();
          } else if (consumeHorizontal) {
            childWidthSize -= child.getMeasuredWidth();
          }
        }
      }
    }

    mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
    mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);

    // Make sure we have created all fragments that we need to have shown.
    mInLayout = true;
    populate();
    mInLayout = false;

    // Page views next.
    size = getChildCount();
    for (int i = 0; i < size; ++i) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        if (DEBUG) {
          Log.v(TAG, "Measuring #" + i + " " + child
              + ": " + mChildWidthMeasureSpec);
        }

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (lp == null || !lp.isDecor) {
          if (mOrientation == Orientation.VERTICAL) {
            final int heightSpec = MeasureSpec.makeMeasureSpec(
                (int) (childHeightSize * lp.heightFactor), MeasureSpec.EXACTLY);
            child.measure(mChildWidthMeasureSpec, heightSpec);
          } else {

            final int widthSpec = MeasureSpec.makeMeasureSpec(
                (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
            child.measure(widthSpec, mChildHeightMeasureSpec);
          }
        }
      }
    }
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    // Make sure scroll position is set correctly.
    if (mOrientation == Orientation.VERTICAL) {
      if (h != oldh) {
        recomputeScrollPosition(h, oldh, mPageMargin, mPageMargin);
      }
    } else {
      if (w != oldw) {
        recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
      }
    }
  }

  private void recomputeScrollPosition(int size, int oldSize, int margin, int oldMargin) {
    if (mOrientation == Orientation.VERTICAL) {
      if (oldSize > 0 && !mItems.isEmpty()) {
        final int heightWithMargin = size - getPaddingTop() - getPaddingBottom() + margin;
        final int oldHeightWithMargin = oldSize - getPaddingTop() - getPaddingBottom()
            + oldMargin;
        final int ypos = getScrollY();
        final float pageOffset = (float) ypos / oldHeightWithMargin;
        final int newOffsetPixels = (int) (pageOffset * heightWithMargin);

        scrollTo(getScrollX(), newOffsetPixels);
        if (!mScroller.isFinished()) {
          // We now return to your regularly scheduled scroll, already in progress.
          final int newDuration = mScroller.getDuration() - mScroller.timePassed();
          ItemInfo targetInfo = infoForPosition(mCurItem);
          mScroller.startScroll(0, newOffsetPixels,
              0, (int) (targetInfo.offset * size), newDuration);
        }
      } else {
        final ItemInfo ii = infoForPosition(mCurItem);
        final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
        final int scrollPos = (int) (scrollOffset *
            (size - getPaddingTop() - getPaddingBottom()));
        if (scrollPos != getScrollY()) {
          completeScroll(false);
          scrollTo(getScrollX(), scrollPos);
        }
      }
    } else {
      if (oldSize > 0 && !mItems.isEmpty()) {
        final int widthWithMargin = size - getPaddingLeft() - getPaddingRight() + margin;
        final int oldWidthWithMargin = oldSize - getPaddingLeft() - getPaddingRight()
            + oldMargin;
        final int xpos = getScrollX();
        final float pageOffset = (float) xpos / oldWidthWithMargin;
        final int newOffsetPixels = (int) (pageOffset * widthWithMargin);

        scrollTo(newOffsetPixels, getScrollY());
        if (!mScroller.isFinished()) {
          // We now return to your regularly scheduled scroll, already in progress.
          final int newDuration = mScroller.getDuration() - mScroller.timePassed();
          ItemInfo targetInfo = infoForPosition(mCurItem);
          mScroller.startScroll(newOffsetPixels, 0,
              (int) (targetInfo.offset * size), 0, newDuration);
        }
      } else {
        final ItemInfo ii = infoForPosition(mCurItem);
        final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
        final int scrollPos = (int) (scrollOffset *
            (size - getPaddingLeft() - getPaddingRight()));
        if (scrollPos != getScrollX()) {
          completeScroll(false);
          scrollTo(scrollPos, getScrollY());
        }
      }
    }
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = getChildCount();
    int width = r - l;
    int height = b - t;
    int paddingLeft = getPaddingLeft();
    int paddingTop = getPaddingTop();
    int paddingRight = getPaddingRight();
    int paddingBottom = getPaddingBottom();
    final int scroll = (mOrientation == Orientation.VERTICAL) ? getScrollY() : getScrollX();

    int decorCount = 0;

    // First pass - decor views. We need to do this in two passes so that
    // we have the proper offsets for non-decor views later.
    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        int childLeft = 0;
        int childTop = 0;
        if (lp.isDecor) {
          final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
          final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
          switch (hgrav) {
            default:
              childLeft = paddingLeft;
              break;
            case Gravity.LEFT:
              childLeft = paddingLeft;
              paddingLeft += child.getMeasuredWidth();
              break;
            case Gravity.CENTER_HORIZONTAL:
              childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
                  paddingLeft);
              break;
            case Gravity.RIGHT:
              childLeft = width - paddingRight - child.getMeasuredWidth();
              paddingRight += child.getMeasuredWidth();
              break;
          }
          switch (vgrav) {
            default:
              childTop = paddingTop;
              break;
            case Gravity.TOP:
              childTop = paddingTop;
              paddingTop += child.getMeasuredHeight();
              break;
            case Gravity.CENTER_VERTICAL:
              childTop = Math.max((height - child.getMeasuredHeight()) / 2,
                  paddingTop);
              break;
            case Gravity.BOTTOM:
              childTop = height - paddingBottom - child.getMeasuredHeight();
              paddingBottom += child.getMeasuredHeight();
              break;
          }
          if (mOrientation == Orientation.VERTICAL) {
            childTop += scroll;
          } else {
            childLeft += scroll;
          }
          child.layout(childLeft, childTop,
              childLeft + child.getMeasuredWidth(),
              childTop + child.getMeasuredHeight());
          decorCount++;
        }
      }
    }

    final int childSize =
        (mOrientation == Orientation.VERTICAL) ? height - paddingTop - paddingBottom
            : width - paddingLeft - paddingRight;
    // Page views. Do this once we have the right padding offsets from above.
    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        ItemInfo ii;
        if (!lp.isDecor && (ii = infoForChild(child)) != null) {
          int topLeftoff = (int) (childSize * ii.offset);
          int childLeft;
          int childTop;
          if (mOrientation == Orientation.VERTICAL) {
            childLeft = paddingLeft;
            childTop = paddingTop + topLeftoff;
            if (lp.needsMeasure) {
              // This was added during layout and needs measurement.
              // Do it now that we know what we're working with.
              lp.needsMeasure = false;
              final int widthSpec = MeasureSpec.makeMeasureSpec(
                  (int) (width - paddingLeft - paddingRight),
                  MeasureSpec.EXACTLY);
              final int heightSpec = MeasureSpec.makeMeasureSpec(
                  (int) (childSize * lp.heightFactor),
                  MeasureSpec.EXACTLY);
              child.measure(widthSpec, heightSpec);
            }
          } else {
            childLeft = paddingLeft + topLeftoff;
            childTop = paddingTop;
            if (lp.needsMeasure) {
              // This was added during layout and needs measurement.
              // Do it now that we know what we're working with.
              lp.needsMeasure = false;
              final int widthSpec = MeasureSpec.makeMeasureSpec(
                  (int) (childSize * lp.widthFactor),
                  MeasureSpec.EXACTLY);
              final int heightSpec = MeasureSpec.makeMeasureSpec(
                  (int) (height - paddingTop - paddingBottom),
                  MeasureSpec.EXACTLY);
              child.measure(widthSpec, heightSpec);
            }
          }
          if (DEBUG) {
            Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
                + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
                + "x" + child.getMeasuredHeight());
          }
          child.layout(childLeft, childTop,
              childLeft + child.getMeasuredWidth(),
              childTop + child.getMeasuredHeight());
        }
      }
    }
    mTopLeftPageBounds = (mOrientation == Orientation.VERTICAL) ? paddingLeft : paddingTop;
    mBottomRightPageBounds =
        (mOrientation == Orientation.VERTICAL) ? width - paddingRight : height - paddingBottom;
    mDecorChildCount = decorCount;

    if (mFirstLayout) {
      scrollToItem(mCurItem, false, 0, false);
    }
    mFirstLayout = false;
  }

  @Override
  public void computeScroll() {
    if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
      int oldX = getScrollX();
      int oldY = getScrollY();
      int x = mScroller.getCurrX();
      int y = mScroller.getCurrY();

      if (oldX != x || oldY != y) {
        scrollTo(x, y);
        if (mOrientation == Orientation.VERTICAL) {
          if (!pageScrolled(y)) {
            mScroller.abortAnimation();
            scrollTo(x, 0);
          }
        } else {
          if (!pageScrolled(x)) {
            mScroller.abortAnimation();
            scrollTo(0, y);
          }
        }
      }

      // Keep on drawing until the animation has finished.
      ViewCompat.postInvalidateOnAnimation(this);
      return;
    }

    // Done with scroll, clean up state.
    completeScroll(true);
  }

  private boolean pageScrolled(int pos) {
    if (mItems.size() == 0) {
      mCalledSuper = false;
      onPageScrolled(0, 0, 0);
      if (!mCalledSuper) {
        throw new IllegalStateException(
            "onPageScrolled did not call superclass implementation");
      }
      return false;
    }
    final ItemInfo ii = infoForCurrentScrollPosition();
    final int size = getClientSize();
    final int sizeWithMargin = size + mPageMargin;
    final float marginOffset = (float) mPageMargin / size;
    final int currentPage = ii.position;
    final float pageOffset = (((float) pos / size) - ii.offset) / (ii.sizeFactor + marginOffset);
    final int offsetPixels = (int) (pageOffset * sizeWithMargin);

    mCalledSuper = false;
    onPageScrolled(currentPage, pageOffset, offsetPixels);
    if (!mCalledSuper) {
      throw new IllegalStateException(
          "onPageScrolled did not call superclass implementation");
    }
    return true;
  }

  /**
   * This method will be invoked when the current page is scrolled, either as part
   * of a programmatically initiated smooth scroll or a user initiated touch scroll.
   * If you override this method you must call through to the superclass implementation
   * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
   * returns.
   *
   * @param position Position index of the first page currently being displayed.
   * Page position+1 will be visible if positionOffset is nonzero.
   * @param offset Value from [0, 1) indicating the offset from the page at position.
   * @param offsetPixels Value in pixels indicating the offset from position.
   */
  protected void onPageScrolled(int position, float offset, int offsetPixels) {
    // Offset any decor views if needed - keep them on-screen at all times.
    if (mDecorChildCount > 0) {
      if (mOrientation == Orientation.VERTICAL) {
        final int scrollY = getScrollY();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        final int height = getHeight();
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
          final View child = getChildAt(i);
          final LayoutParams lp = (LayoutParams) child.getLayoutParams();
          if (!lp.isDecor) continue;

          final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
          int childTop = 0;
          switch (vgrav) {
            default:
              childTop = paddingTop;
              break;
            case Gravity.TOP:
              childTop = paddingTop;
              paddingTop += child.getHeight();
              break;
            case Gravity.CENTER_VERTICAL:
              childTop = Math.max((height - child.getMeasuredHeight()) / 2,
                  paddingTop);
              break;
            case Gravity.BOTTOM:
              childTop = height - paddingBottom - child.getMeasuredHeight();
              paddingBottom += child.getMeasuredHeight();
              break;
          }
          childTop += scrollY;

          final int childOffset = childTop - child.getTop();
          if (childOffset != 0) {
            child.offsetTopAndBottom(childOffset);
          }
        }
      } else {
        final int scrollX = getScrollX();
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        final int width = getWidth();
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
          final View child = getChildAt(i);
          final LayoutParams lp = (LayoutParams) child.getLayoutParams();
          if (!lp.isDecor) continue;

          final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
          int childLeft = 0;
          switch (hgrav) {
            default:
              childLeft = paddingLeft;
              break;
            case Gravity.LEFT:
              childLeft = paddingLeft;
              paddingLeft += child.getWidth();
              break;
            case Gravity.CENTER_HORIZONTAL:
              childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
                  paddingLeft);
              break;
            case Gravity.RIGHT:
              childLeft = width - paddingRight - child.getMeasuredWidth();
              paddingRight += child.getMeasuredWidth();
              break;
          }
          childLeft += scrollX;

          final int childOffset = childLeft - child.getLeft();
          if (childOffset != 0) {
            child.offsetLeftAndRight(childOffset);
          }
        }
      }
    }

    if (mOnPageChangeListener != null) {
      mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
    }
    if (mInternalPageChangeListener != null) {
      mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
    }

    if (mPageTransformer != null) {
      final int scroll = (mOrientation == Orientation.VERTICAL) ? getScrollY() : getScrollX();
      final int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        if (lp.isDecor) continue;

        final float transformPos =
            (float) (((mOrientation == Orientation.VERTICAL) ? child.getTop() : child.getLeft())
                - scroll) / getClientSize();
        mPageTransformer.transformPage(child, transformPos);
      }
    }

    mCalledSuper = true;
  }

  private void completeScroll(boolean postEvents) {
    boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
    if (needPopulate) {
      // Done with scroll, no longer want to cache view drawing.
      setScrollingCacheEnabled(false);
      mScroller.abortAnimation();
      int oldX = getScrollX();
      int oldY = getScrollY();
      int x = mScroller.getCurrX();
      int y = mScroller.getCurrY();
      if (oldX != x || oldY != y) {
        scrollTo(x, y);
      }
    }
    mPopulatePending = false;
    for (int i = 0; i < mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      if (ii.scrolling) {
        needPopulate = true;
        ii.scrolling = false;
      }
    }
    if (needPopulate) {
      if (postEvents) {
        ViewCompat.postOnAnimation(this, mEndScrollRunnable);
      } else {
        mEndScrollRunnable.run();
      }
    }
  }

  private boolean isGutterDrag(float axis, float dAxis) {
    return (axis < mGutterSize && dAxis > 0) || (axis
        > (mOrientation == Orientation.VERTICAL ? getHeight() : getWidth()) - mGutterSize
        && dAxis < 0);
  }

  private void enableLayers(boolean enable) {
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      final int layerType = enable ?
          ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
      ViewCompat.setLayerType(getChildAt(i), layerType, null);
    }
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    /*
     * This method JUST determines whether we want to intercept the motion.
     * If we return true, onMotionEvent will be called and we do the actual
     * scrolling there.
     */

    final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

    // Always take care of the touch gesture being complete.
    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
      // Release the drag.
      if (DEBUG) Log.v(TAG, "Intercept done!");
      mIsBeingDragged = false;
      mIsUnableToDrag = false;
      mActivePointerId = INVALID_POINTER;
      if (mVelocityTracker != null) {
        mVelocityTracker.recycle();
        mVelocityTracker = null;
      }
      return false;
    }

    // Nothing more to do here if we have decided whether or not we
    // are dragging.
    if (action != MotionEvent.ACTION_DOWN) {
      if (mIsBeingDragged) {
        if (DEBUG) Log.v(TAG, "Intercept returning true!");
        return true;
      }
      if (mIsUnableToDrag) {
        if (DEBUG) Log.v(TAG, "Intercept returning false!");
        return false;
      }
    }

    switch (action) {
      case MotionEvent.ACTION_MOVE: {
        /*
         * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
         * whether the user has moved far enough from his original down touch.
         */

        /*
         * Locally do absolute value. mLastMotionY is set to the y value
         * of the down event.
         */
        final int activePointerId = mActivePointerId;
        if (activePointerId == INVALID_POINTER) {
          // If we don't have a valid id, the touch down wasn't on content.
          break;
        }

        final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
        if (mOrientation == Orientation.VERTICAL) {
          final float y = MotionEventCompat.getY(ev, pointerIndex);
          final float dy = y - mLastMotionY;
          final float yDiff = Math.abs(dy);
          final float x = MotionEventCompat.getX(ev, pointerIndex);
          final float xDiff = Math.abs(x - mInitialMotionX);
          if (DEBUG) {
            Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
          }

          if (dy != 0 && !isGutterDrag(mLastMotionY, dy) &&
              canScroll(this, false, (int) dy, (int) x, (int) y)) {
            // Nested view has scrollable area under this point. Let it be handled there.
            mLastMotionX = x;
            mLastMotionY = y;
            mIsUnableToDrag = true;
            return false;
          }
          if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {
            if (DEBUG) Log.v(TAG, "Starting drag!");
            mIsBeingDragged = true;
            requestParentDisallowInterceptTouchEvent(true);
            setScrollState(SCROLL_STATE_DRAGGING);
            mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :
                mInitialMotionY - mTouchSlop;
            mLastMotionX = x;
            setScrollingCacheEnabled(true);
          } else if (xDiff > mTouchSlop) {
            // The finger has moved enough in the vertical
            // direction to be counted as a drag...  abort
            // any attempt to drag horizontally, to work correctly
            // with children that have scrolling containers.
            if (DEBUG) Log.v(TAG, "Starting unable to drag!");
            mIsUnableToDrag = true;
          }
          if (mIsBeingDragged) {
            // Scroll to follow the motion event
            if (performDrag(y)) {
              ViewCompat.postInvalidateOnAnimation(this);
            }
          }
        } else {
          final float x = MotionEventCompat.getX(ev, pointerIndex);
          final float dx = x - mLastMotionX;
          final float xDiff = Math.abs(dx);
          final float y = MotionEventCompat.getY(ev, pointerIndex);
          final float yDiff = Math.abs(y - mInitialMotionY);
          if (DEBUG) {
            Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
          }

          if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
              canScroll(this, false, (int) dx, (int) x, (int) y)) {
            // Nested view has scrollable area under this point. Let it be handled there.
            mLastMotionX = x;
            mLastMotionY = y;
            mIsUnableToDrag = true;
            return false;
          }
          if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
            if (DEBUG) Log.v(TAG, "Starting drag!");
            mIsBeingDragged = true;
            requestParentDisallowInterceptTouchEvent(true);
            setScrollState(SCROLL_STATE_DRAGGING);
            mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
                mInitialMotionX - mTouchSlop;
            mLastMotionY = y;
            setScrollingCacheEnabled(true);
          } else if (yDiff > mTouchSlop) {
            // The finger has moved enough in the vertical
            // direction to be counted as a drag...  abort
            // any attempt to drag horizontally, to work correctly
            // with children that have scrolling containers.
            if (DEBUG) Log.v(TAG, "Starting unable to drag!");
            mIsUnableToDrag = true;
          }
          if (mIsBeingDragged) {
            // Scroll to follow the motion event
            if (performDrag(x)) {
              ViewCompat.postInvalidateOnAnimation(this);
            }
          }
        }
        break;
      }

      case MotionEvent.ACTION_DOWN: {
        /*
         * Remember location of down touch.
         * ACTION_DOWN always refers to pointer index 0.
         */
        mLastMotionX = mInitialMotionX = ev.getX();
        mLastMotionY = mInitialMotionY = ev.getY();
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        mIsUnableToDrag = false;

        mScroller.computeScrollOffset();
        if (mOrientation == Orientation.VERTICAL) {
          if (mScrollState == SCROLL_STATE_SETTLING &&
              Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {
            // Let the user 'catch' the pager as it animates.
            mScroller.abortAnimation();
            mPopulatePending = false;
            populate();
            mIsBeingDragged = true;
            requestParentDisallowInterceptTouchEvent(true);
            setScrollState(SCROLL_STATE_DRAGGING);
          } else {
            completeScroll(false);
            mIsBeingDragged = false;
          }
        } else {
          if (mScrollState == SCROLL_STATE_SETTLING &&
              Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
            // Let the user 'catch' the pager as it animates.
            mScroller.abortAnimation();
            mPopulatePending = false;
            populate();
            mIsBeingDragged = true;
            requestParentDisallowInterceptTouchEvent(true);
            setScrollState(SCROLL_STATE_DRAGGING);
          } else {
            completeScroll(false);
            mIsBeingDragged = false;
          }
        }

        if (DEBUG) {
          Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
              + " mIsBeingDragged=" + mIsBeingDragged
              + "mIsUnableToDrag=" + mIsUnableToDrag);
        }
        break;
      }

      case MotionEventCompat.ACTION_POINTER_UP:
        onSecondaryPointerUp(ev);
        break;
    }

    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);

    /*
     * The only time we want to intercept motion events is if we are in the
     * drag mode.
     */
    return mIsBeingDragged;
  }

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (mFakeDragging) {
      // A fake drag is in progress already, ignore this real one
      // but still eat the touch events.
      // (It is likely that the user is multi-touching the screen.)
      return true;
    }

    if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
      // Don't handle edge touches immediately -- they may actually belong to one of our
      // descendants.
      return false;
    }

    if (mAdapter == null || mAdapter.getCount() == 0) {
      // Nothing to present or scroll; nothing to touch.
      return false;
    }

    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);

    final int action = ev.getAction();
    boolean needsInvalidate = false;

    switch (action & MotionEventCompat.ACTION_MASK) {
      case MotionEvent.ACTION_DOWN: {
        mScroller.abortAnimation();
        mPopulatePending = false;
        populate();

        // Remember where the motion event started
        mLastMotionX = mInitialMotionX = ev.getX();
        mLastMotionY = mInitialMotionY = ev.getY();
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        break;
      }
      case MotionEvent.ACTION_MOVE:
        if (!mIsBeingDragged) {
          final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
          final float y = MotionEventCompat.getY(ev, pointerIndex);
          final float yDiff = Math.abs(y - mLastMotionY);
          final float x = MotionEventCompat.getX(ev, pointerIndex);
          final float xDiff = Math.abs(x - mLastMotionX);
          if (DEBUG) {
            Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
          }
          if (mOrientation == Orientation.VERTICAL) {
            if (yDiff > mTouchSlop && yDiff > xDiff) {
              if (DEBUG) Log.v(TAG, "Starting drag!");
              mIsBeingDragged = true;
              requestParentDisallowInterceptTouchEvent(true);
              mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :
                  mInitialMotionY - mTouchSlop;
              mLastMotionX = x;
              setScrollState(SCROLL_STATE_DRAGGING);
              setScrollingCacheEnabled(true);

              // Disallow Parent Intercept, just in case
              ViewParent parent = getParent();
              if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(true);
              }
            }
          } else {
            if (xDiff > mTouchSlop && xDiff > yDiff) {
              if (DEBUG) Log.v(TAG, "Starting drag!");
              mIsBeingDragged = true;
              requestParentDisallowInterceptTouchEvent(true);
              mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
                  mInitialMotionX - mTouchSlop;
              mLastMotionY = y;
              setScrollState(SCROLL_STATE_DRAGGING);
              setScrollingCacheEnabled(true);

              // Disallow Parent Intercept, just in case
              ViewParent parent = getParent();
              if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(true);
              }
            }
          }
        }
        // Not else! Note that mIsBeingDragged can be set above.
        if (mIsBeingDragged) {
          // Scroll to follow the motion event
          final int activePointerIndex = MotionEventCompat.findPointerIndex(
              ev, mActivePointerId);
          if (mOrientation == Orientation.VERTICAL) {
            final float y = MotionEventCompat.getY(ev, activePointerIndex);
            needsInvalidate |= performDrag(y);
          } else {
            final float x = MotionEventCompat.getX(ev, activePointerIndex);
            needsInvalidate |= performDrag(x);
          }
        }
        break;
      case MotionEvent.ACTION_UP:
        if (mIsBeingDragged) {
          int currentPage;
          int initialVelocity;
          int totalDelta;
          float pageOffset;
          if (mOrientation == Orientation.VERTICAL) {
            final VelocityTracker velocityTracker = mVelocityTracker;
            velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
                velocityTracker, mActivePointerId);
            mPopulatePending = true;
            final int height = getClientSize();
            final int scrollY = getScrollY();
            final ItemInfo ii = infoForCurrentScrollPosition();
            currentPage = ii.position;
            pageOffset = (((float) scrollY / height) - ii.offset) / ii.sizeFactor;
            final int activePointerIndex =
                MotionEventCompat.findPointerIndex(ev, mActivePointerId);
            final float y = MotionEventCompat.getY(ev, activePointerIndex);
            totalDelta = (int) (y - mInitialMotionY);
          } else {
            final VelocityTracker velocityTracker = mVelocityTracker;
            velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
                velocityTracker, mActivePointerId);
            mPopulatePending = true;
            final int width = getClientSize();
            final int scrollX = getScrollX();
            final ItemInfo ii = infoForCurrentScrollPosition();
            currentPage = ii.position;
            pageOffset = (((float) scrollX / width) - ii.offset) / ii.sizeFactor;
            final int activePointerIndex =
                MotionEventCompat.findPointerIndex(ev, mActivePointerId);
            final float x = MotionEventCompat.getX(ev, activePointerIndex);
            totalDelta = (int) (x - mInitialMotionX);
          }
          int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
              totalDelta);
          setCurrentItemInternal(nextPage, true, true, initialVelocity);

          mActivePointerId = INVALID_POINTER;
          endDrag();
          needsInvalidate = mTopLeftEdge.onRelease() | mRightBottomEdge.onRelease();
        }
        break;
      case MotionEvent.ACTION_CANCEL:
        if (mIsBeingDragged) {
          scrollToItem(mCurItem, true, 0, false);
          mActivePointerId = INVALID_POINTER;
          endDrag();
          needsInvalidate = mTopLeftEdge.onRelease() | mRightBottomEdge.onRelease();
        }
        break;
      case MotionEventCompat.ACTION_POINTER_DOWN: {
        int index;
        if (mOrientation == Orientation.VERTICAL) {
          index = MotionEventCompat.getActionIndex(ev);
          final float y = MotionEventCompat.getY(ev, index);
          mLastMotionY = y;
        } else {
          index = MotionEventCompat.getActionIndex(ev);
          final float x = MotionEventCompat.getX(ev, index);
          mLastMotionX = x;
        }
        mActivePointerId = MotionEventCompat.getPointerId(ev, index);
        break;
      }
      case MotionEventCompat.ACTION_POINTER_UP:
        onSecondaryPointerUp(ev);
        if (mOrientation == Orientation.VERTICAL) {
          mLastMotionY = MotionEventCompat.getY(ev,
              MotionEventCompat.findPointerIndex(ev, mActivePointerId));
        } else {
          mLastMotionX = MotionEventCompat.getX(ev,
              MotionEventCompat.findPointerIndex(ev, mActivePointerId));
        }
        break;
    }
    if (needsInvalidate) {
      ViewCompat.postInvalidateOnAnimation(this);
    }
    return true;
  }

  private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
    final ViewParent parent = getParent();
    if (parent != null) {
      parent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
  }

  private boolean performDrag(float dimen) {
    boolean needsInvalidate = false;

    if (mOrientation == Orientation.VERTICAL) {
      float y = dimen;
      final float deltaY = mLastMotionY - y;
      mLastMotionY = y;

      float oldScrollY = getScrollY();
      float scrollY = oldScrollY + deltaY;
      final int height = getClientSize();

      float topBound = height * mFirstOffset;
      float bottomBound = height * mLastOffset;
      boolean topAbsolute = true;
      boolean bottomAbsolute = true;

      final ItemInfo firstItem = mItems.get(0);
      final ItemInfo lastItem = mItems.get(mItems.size() - 1);
      if (firstItem.position != 0) {
        topAbsolute = false;
        topBound = firstItem.offset * height;
      }
      if (lastItem.position != mAdapter.getCount() - 1) {
        bottomAbsolute = false;
        bottomBound = lastItem.offset * height;
      }

      if (scrollY < topBound) {
        if (topAbsolute) {
          float over = topBound - scrollY;
          needsInvalidate = mTopLeftEdge.onPull(Math.abs(over) / height);
        }
        scrollY = topBound;
      } else if (scrollY > bottomBound) {
        if (bottomAbsolute) {
          float over = scrollY - bottomBound;
          needsInvalidate = mRightBottomEdge.onPull(Math.abs(over) / height);
        }
        scrollY = bottomBound;
      }
      // Don't lose the rounded component
      mLastMotionX += scrollY - (int) scrollY;
      scrollTo(getScrollX(), (int) scrollY);
      pageScrolled((int) scrollY);
    } else {
      float x = dimen;

      final float deltaX = mLastMotionX - x;
      mLastMotionX = x;

      float oldScrollX = getScrollX();
      float scrollX = oldScrollX + deltaX;
      final int width = getClientSize();

      float leftBound = width * mFirstOffset;
      float rightBound = width * mLastOffset;
      boolean leftAbsolute = true;
      boolean rightAbsolute = true;

      final ItemInfo firstItem = mItems.get(0);
      final ItemInfo lastItem = mItems.get(mItems.size() - 1);
      if (firstItem.position != 0) {
        leftAbsolute = false;
        leftBound = firstItem.offset * width;
      }
      if (lastItem.position != mAdapter.getCount() - 1) {
        rightAbsolute = false;
        rightBound = lastItem.offset * width;
      }

      if (scrollX < leftBound) {
        if (leftAbsolute) {
          float over = leftBound - scrollX;
          needsInvalidate = mTopLeftEdge.onPull(Math.abs(over) / width);
        }
        scrollX = leftBound;
      } else if (scrollX > rightBound) {
        if (rightAbsolute) {
          float over = scrollX - rightBound;
          needsInvalidate = mRightBottomEdge.onPull(Math.abs(over) / width);
        }
        scrollX = rightBound;
      }
      // Don't lose the rounded component
      mLastMotionX += scrollX - (int) scrollX;
      scrollTo((int) scrollX, getScrollY());
      pageScrolled((int) scrollX);
    }

    return needsInvalidate;
  }

  /**
   * @return Info about the page at the current scroll position.
   * This can be synthetic for a missing middle page; the 'object' field can be null.
   */
  private ItemInfo infoForCurrentScrollPosition() {
    final int size = getClientSize();
    final float scrollOffset =
        size > 0 ? (float) ((mOrientation == Orientation.VERTICAL) ? getScrollY() : getScrollX())
            / size : 0;
    final float marginOffset = size > 0 ? (float) mPageMargin / size : 0;
    int lastPos = -1;
    float lastOffset = 0.f;
    float lastSize = 0.f;
    boolean first = true;

    ItemInfo lastItem = null;
    for (int i = 0; i < mItems.size(); i++) {
      ItemInfo ii = mItems.get(i);
      float offset;
      if (!first && ii.position != lastPos + 1) {
        // Create a synthetic item for a missing page.
        ii = mTempItem;
        ii.offset = lastOffset + lastSize + marginOffset;
        ii.position = lastPos + 1;
        ii.sizeFactor = mAdapter.getPageWidth(ii.position);
        i--;
      }
      offset = ii.offset;

      final float topLeftBound = offset;
      final float bottomRightBound = offset + ii.sizeFactor + marginOffset;
      if (first || scrollOffset >= topLeftBound) {
        if (scrollOffset < bottomRightBound || i == mItems.size() - 1) {
          return ii;
        }
      } else {
        return lastItem;
      }
      first = false;
      lastPos = ii.position;
      lastOffset = offset;
      lastSize = ii.sizeFactor;
      lastItem = ii;
    }

    return lastItem;
  }

  private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaDimen) {
    int targetPage;
    if (Math.abs(deltaDimen) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
      targetPage = velocity > 0 ? currentPage : currentPage + 1;
    } else {
      final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
      targetPage = (int) (currentPage + pageOffset + truncator);
    }

    if (mItems.size() > 0) {
      final ItemInfo firstItem = mItems.get(0);
      final ItemInfo lastItem = mItems.get(mItems.size() - 1);

      // Only let the user target pages we have items for
      targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
    }

    return targetPage;
  }

  @Override
  public void draw(Canvas canvas) {
    super.draw(canvas);
    boolean needsInvalidate = false;

    final int overScrollMode = ViewCompat.getOverScrollMode(this);
    if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
        (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
            mAdapter != null && mAdapter.getCount() > 1)) {
      if (mOrientation == Orientation.VERTICAL) {
        if (!mTopLeftEdge.isFinished()) {
          final int restoreCount = canvas.save();
          final int height = getHeight();
          final int width = getWidth() - getPaddingLeft() - getPaddingRight();

          canvas.translate(getPaddingLeft(), mFirstOffset * height);
          mTopLeftEdge.setSize(width, height);
          needsInvalidate |= mTopLeftEdge.draw(canvas);
          canvas.restoreToCount(restoreCount);
        }
        if (!mRightBottomEdge.isFinished()) {
          final int restoreCount = canvas.save();
          final int height = getHeight();
          final int width = getWidth() - getPaddingLeft() - getPaddingRight();

          canvas.rotate(180);
          canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);
          mRightBottomEdge.setSize(width, height);
          needsInvalidate |= mRightBottomEdge.draw(canvas);
          canvas.restoreToCount(restoreCount);
        }
      } else {
        if (!mTopLeftEdge.isFinished()) {
          final int restoreCount = canvas.save();
          final int height = getHeight() - getPaddingTop() - getPaddingBottom();
          final int width = getWidth();

          canvas.rotate(270);
          canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
          mTopLeftEdge.setSize(height, width);
          needsInvalidate |= mTopLeftEdge.draw(canvas);
          canvas.restoreToCount(restoreCount);
        }
        if (!mRightBottomEdge.isFinished()) {
          final int restoreCount = canvas.save();
          final int width = getWidth();
          final int height = getHeight() - getPaddingTop() - getPaddingBottom();

          canvas.rotate(90);
          canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
          mRightBottomEdge.setSize(height, width);
          needsInvalidate |= mRightBottomEdge.draw(canvas);
          canvas.restoreToCount(restoreCount);
        }
      }
    } else {
      mTopLeftEdge.finish();
      mRightBottomEdge.finish();
    }

    if (needsInvalidate) {
      // Keep animating
      ViewCompat.postInvalidateOnAnimation(this);
    }
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // Draw the margin drawable between pages if needed.
    if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
      if (mOrientation == Orientation.VERTICAL) {
        final int scrollY = getScrollY();
        final int height = getHeight();

        final float marginOffset = (float) mPageMargin / height;
        int itemIndex = 0;
        ItemInfo ii = mItems.get(0);
        float offset = ii.offset;
        final int itemCount = mItems.size();
        final int firstPos = ii.position;
        final int lastPos = mItems.get(itemCount - 1).position;
        for (int pos = firstPos; pos < lastPos; pos++) {
          while (pos > ii.position && itemIndex < itemCount) {
            ii = mItems.get(++itemIndex);
          }

          float drawAt;
          if (pos == ii.position) {
            drawAt = (ii.offset + ii.sizeFactor) * height;
            offset = ii.offset + ii.sizeFactor + marginOffset;
          } else {
            float heightFactor = mAdapter.getPageWidth(pos);
            drawAt = (offset + heightFactor) * height;
            offset += heightFactor + marginOffset;
          }

          if (drawAt + mPageMargin > scrollY) {
            mMarginDrawable.setBounds(mTopLeftPageBounds, (int) drawAt,
                mBottomRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));
            mMarginDrawable.draw(canvas);
          }

          if (drawAt > scrollY + height) {
            break; // No more visible, no sense in continuing
          }
        }
      } else {
        final int scrollX = getScrollX();
        final int width = getWidth();

        final float marginOffset = (float) mPageMargin / width;
        int itemIndex = 0;
        ItemInfo ii = mItems.get(0);
        float offset = ii.offset;
        final int itemCount = mItems.size();
        final int firstPos = ii.position;
        final int lastPos = mItems.get(itemCount - 1).position;
        for (int pos = firstPos; pos < lastPos; pos++) {
          while (pos > ii.position && itemIndex < itemCount) {
            ii = mItems.get(++itemIndex);
          }

          float drawAt;
          if (pos == ii.position) {
            drawAt = (ii.offset + ii.sizeFactor) * width;
            offset = ii.offset + ii.sizeFactor + marginOffset;
          } else {
            float widthFactor = mAdapter.getPageWidth(pos);
            drawAt = (offset + widthFactor) * width;
            offset += widthFactor + marginOffset;
          }

          if (drawAt + mPageMargin > scrollX) {
            mMarginDrawable.setBounds((int) drawAt, mTopLeftPageBounds,
                (int) (drawAt + mPageMargin + 0.5f), mBottomRightPageBounds);
            mMarginDrawable.draw(canvas);
          }

          if (drawAt > scrollX + width) {
            break; // No more visible, no sense in continuing
          }
        }
      }
    }
  }

  /**
   * Start a fake drag of the pager.
   * <p/>
   * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
   * with the touch scrolling of another view, while still letting the ViewPager
   * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
   * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
   * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
   * <p/>
   * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
   * is already in progress, this method will return false.
   *
   * @return true if the fake drag began successfully, false if it could not be started.
   * @see #fakeDragBy(float)
   * @see #endFakeDrag()
   */
  public boolean beginFakeDrag() {
    if (mIsBeingDragged) {
      return false;
    }
    mFakeDragging = true;
    setScrollState(SCROLL_STATE_DRAGGING);
    if (mOrientation == Orientation.VERTICAL) {
      mInitialMotionY = mLastMotionY = 0;
    } else {
      mInitialMotionX = mLastMotionX = 0;
    }
    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    } else {
      mVelocityTracker.clear();
    }
    final long time = SystemClock.uptimeMillis();
    final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
    mVelocityTracker.addMovement(ev);
    ev.recycle();
    mFakeDragBeginTime = time;
    return true;
  }

  /**
   * End a fake drag of the pager.
   *
   * @see #beginFakeDrag()
   * @see #fakeDragBy(float)
   */
  public void endFakeDrag() {
    if (!mFakeDragging) {
      throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
    }

    final VelocityTracker velocityTracker = mVelocityTracker;
    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
    if (mOrientation == Orientation.VERTICAL) {
      int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
          velocityTracker, mActivePointerId);
      mPopulatePending = true;
      final int size = getClientSize();
      final int scrollY = getScrollY();
      final ItemInfo ii = infoForCurrentScrollPosition();
      final int currentPage = ii.position;
      final float pageOffset = (((float) scrollY / size) - ii.offset) / ii.sizeFactor;
      final int totalDelta = (int) (mLastMotionY - mInitialMotionY);

      int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
          totalDelta);
      setCurrentItemInternal(nextPage, true, true, initialVelocity);
    } else {
      int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
          velocityTracker, mActivePointerId);
      mPopulatePending = true;
      final int size = getClientSize();
      final int scrollX = getScrollX();
      final ItemInfo ii = infoForCurrentScrollPosition();
      final int currentPage = ii.position;
      final float pageOffset = (((float) scrollX / size) - ii.offset) / ii.sizeFactor;
      final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
      int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
          totalDelta);
      setCurrentItemInternal(nextPage, true, true, initialVelocity);
    }
    endDrag();

    mFakeDragging = false;
  }

  /**
   * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
   *
   * @param offset Offset in pixels to drag by.
   * @see #beginFakeDrag()
   * @see #endFakeDrag()
   */
  public void fakeDragBy(float offset) {
    if (!mFakeDragging) {
      throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
    }

    if (mOrientation == Orientation.VERTICAL) {
      mLastMotionY += offset;

      float oldScrollY = getScrollY();
      float scrollY = oldScrollY - offset;
      final int height = getClientSize();

      float topBound = height * mFirstOffset;
      float bottomBound = height * mLastOffset;

      final ItemInfo firstItem = mItems.get(0);
      final ItemInfo lastItem = mItems.get(mItems.size() - 1);
      if (firstItem.position != 0) {
        topBound = firstItem.offset * height;
      }
      if (lastItem.position != mAdapter.getCount() - 1) {
        bottomBound = lastItem.offset * height;
      }

      if (scrollY < topBound) {
        scrollY = topBound;
      } else if (scrollY > bottomBound) {
        scrollY = bottomBound;
      }
      // Don't lose the rounded component
      mLastMotionY += scrollY - (int) scrollY;
      scrollTo(getScrollX(), (int) scrollY);
      pageScrolled((int) scrollY);

      // Synthesize an event for the VelocityTracker.
      final long time = SystemClock.uptimeMillis();
      final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
          0, mLastMotionY, 0);
      mVelocityTracker.addMovement(ev);
      ev.recycle();
    } else {
      mLastMotionX += offset;

      float oldScrollX = getScrollX();
      float scrollX = oldScrollX - offset;
      final int width = getClientSize();

      float leftBound = width * mFirstOffset;
      float rightBound = width * mLastOffset;

      final ItemInfo firstItem = mItems.get(0);
      final ItemInfo lastItem = mItems.get(mItems.size() - 1);
      if (firstItem.position != 0) {
        leftBound = firstItem.offset * width;
      }
      if (lastItem.position != mAdapter.getCount() - 1) {
        rightBound = lastItem.offset * width;
      }

      if (scrollX < leftBound) {
        scrollX = leftBound;
      } else if (scrollX > rightBound) {
        scrollX = rightBound;
      }
      // Don't lose the rounded component
      mLastMotionX += scrollX - (int) scrollX;
      scrollTo((int) scrollX, getScrollY());
      pageScrolled((int) scrollX);

      // Synthesize an event for the VelocityTracker.
      final long time = SystemClock.uptimeMillis();
      final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
          mLastMotionX, 0, 0);
      mVelocityTracker.addMovement(ev);
      ev.recycle();
    }
  }

  /**
   * Returns true if a fake drag is in progress.
   *
   * @return true if currently in a fake drag, false otherwise.
   * @see #beginFakeDrag()
   * @see #fakeDragBy(float)
   * @see #endFakeDrag()
   */
  public boolean isFakeDragging() {
    return mFakeDragging;
  }

  private void onSecondaryPointerUp(MotionEvent ev) {
    final int pointerIndex = MotionEventCompat.getActionIndex(ev);
    final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
    if (pointerId == mActivePointerId) {
      // This was our active pointer going up. Choose a new
      // active pointer and adjust accordingly.
      final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
      if (mOrientation == Orientation.VERTICAL) {
        mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
      } else {
        mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
      }
      mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
      if (mVelocityTracker != null) {
        mVelocityTracker.clear();
      }
    }
  }

  private void endDrag() {
    mIsBeingDragged = false;
    mIsUnableToDrag = false;

    if (mVelocityTracker != null) {
      mVelocityTracker.recycle();
      mVelocityTracker = null;
    }
  }

  private void setScrollingCacheEnabled(boolean enabled) {
    if (mScrollingCacheEnabled != enabled) {
      mScrollingCacheEnabled = enabled;
      if (USE_CACHE) {
        final int size = getChildCount();
        for (int i = 0; i < size; ++i) {
          final View child = getChildAt(i);
          if (child.getVisibility() != GONE) {
            child.setDrawingCacheEnabled(enabled);
          }
        }
      }
    }
  }

  public boolean internalCanScrollVertically(int direction) {
    if (mAdapter == null) {
      return false;
    }

    final int size = getClientSize();
    final int scroll = (mOrientation == Orientation.VERTICAL) ? getScrollY() : getScrollX();
    if (direction < 0) {
      return (scroll > (int) (size * mFirstOffset));
    } else if (direction > 0) {
      return (scroll < (int) (size * mLastOffset));
    } else {
      return false;
    }
  }

  /**
   * Tests scrollability within child views of v given a delta of dx.
   *
   * @param v View to test for horizontal scrollability
   * @param checkV Whether the view v passed should itself be checked for scrollability (true),
   * or just its children (false).
   * @param delta Delta scrolled in pixels
   * @param x X coordinate of the active touch point
   * @param y Y coordinate of the active touch point
   * @return true if child views of v can be scrolled by delta of dx.
   */
  protected boolean canScroll(View v, boolean checkV, int delta, int x, int y) {
    if (v instanceof ViewGroup) {
      final ViewGroup group = (ViewGroup) v;
      final int scrollX = v.getScrollX();
      final int scrollY = v.getScrollY();
      final int count = group.getChildCount();
      // Count backwards - let topmost views consume scroll distance first.
      for (int i = count - 1; i >= 0; i--) {
        // TODO: Add versioned support here for transformed views.
        // This will not work for transformed views in Honeycomb+
        final View child = group.getChildAt(i);
        if (mOrientation == Orientation.VERTICAL) {
          if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
              x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
              canScroll(child, true, delta, x + scrollX - child.getLeft(),
                  y + scrollY - child.getTop())) {
            return true;
          }
        } else {
          if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
              y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
              canScroll(child, true, delta, x + scrollX - child.getLeft(),
                  y + scrollY - child.getTop())) {
            return true;
          }
        }
      }
    }

    return checkV && ViewCompat.canScrollVertically(v, -delta);
  }

  @Override
  public boolean dispatchKeyEvent(KeyEvent event) {
    // Let the focused view and/or our descendants get the key first
    return super.dispatchKeyEvent(event) || executeKeyEvent(event);
  }

  /**
   * You can call this function yourself to have the scroll view perform
   * scrolling from a key event, just as if the event had been dispatched to
   * it by the view hierarchy.
   *
   * @param event The key event to execute.
   * @return Return true if the event was handled, else false.
   */
  public boolean executeKeyEvent(KeyEvent event) {
    boolean handled = false;
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
      switch (event.getKeyCode()) {
        case KeyEvent.KEYCODE_DPAD_LEFT:
          handled = arrowScroll(FOCUS_LEFT);
          break;
        case KeyEvent.KEYCODE_DPAD_RIGHT:
          handled = arrowScroll(FOCUS_RIGHT);
          break;
        case KeyEvent.KEYCODE_TAB:
          if (Build.VERSION.SDK_INT >= 11) {
            // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
            // before Android 3.0. Ignore the tab key on those devices.
            if (event.hasNoModifiers()) {
              handled = arrowScroll(FOCUS_FORWARD);
            } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
              handled = arrowScroll(FOCUS_BACKWARD);
            }
          }
          break;
      }
    }
    return handled;
  }

  public boolean arrowScroll(int direction) {
    View currentFocused = findFocus();
    if (currentFocused == this) {
      currentFocused = null;
    } else if (currentFocused != null) {
      boolean isChild = false;
      for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
          parent = parent.getParent()) {
        if (parent == this) {
          isChild = true;
          break;
        }
      }
      if (!isChild) {
        // This would cause the focus search down below to fail in fun ways.
        final StringBuilder sb = new StringBuilder();
        sb.append(currentFocused.getClass().getSimpleName());
        for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
            parent = parent.getParent()) {
          sb.append(" => ").append(parent.getClass().getSimpleName());
        }
        Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
            "current focused view " + sb.toString());
        currentFocused = null;
      }
    }

    boolean handled = false;

    View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
        direction);
    if (nextFocused != null && nextFocused != currentFocused) {
      if (direction == View.FOCUS_UP) {
        // If there is nothing to the left, or this is causing us to
        // jump to the right, then what we really want to do is page left.
        if (mOrientation == Orientation.VERTICAL) {
          final int nextTop = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;
          final int currTop = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;
          if (currentFocused != null && nextTop >= currTop) {
            handled = pageBack();
          } else {
            handled = nextFocused.requestFocus();
          }
        } else {
          final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
          final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
          if (currentFocused != null && nextLeft >= currLeft) {
            handled = pageBack();
          } else {
            handled = nextFocused.requestFocus();
          }
        }
      } else if (direction == View.FOCUS_DOWN) {
        // If there is nothing to the right, or this is causing us to
        // jump to the left, then what we really want to do is page right.
        if (mOrientation == Orientation.VERTICAL) {
          final int nextDown = getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;
          final int currDown = getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;
          if (currentFocused != null && nextDown <= currDown) {
            handled = pageForward();
          } else {
            handled = nextFocused.requestFocus();
          }
        } else {
          final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
          final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
          if (currentFocused != null && nextLeft <= currLeft) {
            handled = pageForward();
          } else {
            handled = nextFocused.requestFocus();
          }
        }
      }
    } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {
      // Trying to move left and nothing there; try to page.
      handled = pageBack();
    } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {
      // Trying to move right and nothing there; try to page.
      handled = pageForward();
    }
    if (handled) {
      playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
    }
    return handled;
  }

  private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
    if (outRect == null) {
      outRect = new Rect();
    }
    if (child == null) {
      outRect.set(0, 0, 0, 0);
      return outRect;
    }
    outRect.left = child.getLeft();
    outRect.right = child.getRight();
    outRect.top = child.getTop();
    outRect.bottom = child.getBottom();

    ViewParent parent = child.getParent();
    while (parent instanceof ViewGroup && parent != this) {
      final ViewGroup group = (ViewGroup) parent;
      outRect.left += group.getLeft();
      outRect.right += group.getRight();
      outRect.top += group.getTop();
      outRect.bottom += group.getBottom();

      parent = group.getParent();
    }
    return outRect;
  }

  boolean pageBack() {
    if (mCurItem > 0) {
      setCurrentItem(mCurItem - 1, true);
      return true;
    }
    return false;
  }

  boolean pageForward() {
    if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
      setCurrentItem(mCurItem + 1, true);
      return true;
    }
    return false;
  }

  /**
   * We only want the current page that is being shown to be focusable.
   */
  @Override
  public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
    final int focusableCount = views.size();

    final int descendantFocusability = getDescendantFocusability();

    if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
      for (int i = 0; i < getChildCount(); i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() == VISIBLE) {
          ItemInfo ii = infoForChild(child);
          if (ii != null && ii.position == mCurItem) {
            child.addFocusables(views, direction, focusableMode);
          }
        }
      }
    }

    // we add ourselves (if focusable) in all cases except for when we are
    // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
    // to avoid the focus search finding layouts when a more precise search
    // among the focusable children would be more interesting.
    if (
        descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
            // No focusable descendants
            (focusableCount == views.size())) {
      // Note that we can't call the superclass here, because it will
      // add all views in.  So we need to do the same thing View does.
      if (!isFocusable()) {
        return;
      }
      if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
          isInTouchMode() && !isFocusableInTouchMode()) {
        return;
      }
      if (views != null) {
        views.add(this);
      }
    }
  }

  /**
   * We only want the current page that is being shown to be touchable.
   */
  @Override
  public void addTouchables(ArrayList<View> views) {
    // Note that we don't call super.addTouchables(), which means that
    // we don't call View.addTouchables().  This is okay because a ViewPager
    // is itself not touchable.
    for (int i = 0; i < getChildCount(); i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() == VISIBLE) {
        ItemInfo ii = infoForChild(child);
        if (ii != null && ii.position == mCurItem) {
          child.addTouchables(views);
        }
      }
    }
  }

  /**
   * We only want the current page that is being shown to be focusable.
   */
  @Override
  protected boolean onRequestFocusInDescendants(int direction,
      Rect previouslyFocusedRect) {
    int index;
    int increment;
    int end;
    int count = getChildCount();
    if ((direction & FOCUS_FORWARD) != 0) {
      index = 0;
      increment = 1;
      end = count;
    } else {
      index = count - 1;
      increment = -1;
      end = -1;
    }
    for (int i = index; i != end; i += increment) {
      View child = getChildAt(i);
      if (child.getVisibility() == VISIBLE) {
        ItemInfo ii = infoForChild(child);
        if (ii != null && ii.position == mCurItem) {
          if (child.requestFocus(direction, previouslyFocusedRect)) {
            return true;
          }
        }
      }
    }
    return false;
  }

  @Override
  public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    // Dispatch scroll events from this ViewPager.
    if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
      return super.dispatchPopulateAccessibilityEvent(event);
    }

    // Dispatch all other accessibility events from the current page.
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() == VISIBLE) {
        final ItemInfo ii = infoForChild(child);
        if (ii != null && ii.position == mCurItem &&
            child.dispatchPopulateAccessibilityEvent(event)) {
          return true;
        }
      }
    }

    return false;
  }

  @Override
  protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams();
  }

  @Override
  protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    return generateDefaultLayoutParams();
  }

  @Override
  protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof LayoutParams && super.checkLayoutParams(p);
  }

  @Override
  public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
  }

  class MyAccessibilityDelegate extends AccessibilityDelegateCompat {

    @Override
    public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
      super.onInitializeAccessibilityEvent(host, event);
      event.setClassName(ViewPager.class.getName());
      final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
      recordCompat.setScrollable(canScroll());
      if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
          && mAdapter != null) {
        recordCompat.setItemCount(mAdapter.getCount());
        recordCompat.setFromIndex(mCurItem);
        recordCompat.setToIndex(mCurItem);
      }
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
      super.onInitializeAccessibilityNodeInfo(host, info);
      info.setClassName(ViewPager.class.getName());
      info.setScrollable(canScroll());
      if (mOrientation == Orientation.VERTICAL) {
        if (internalCanScrollVertically(1)) {
          info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
        }
        if (internalCanScrollVertically(-1)) {
          info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
        }
      } else {
        if (canScrollHorizontally(1)) {
          info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
        }
        if (canScrollHorizontally(-1)) {
          info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
        }
      }
    }

    @Override
    public boolean performAccessibilityAction(View host, int action, Bundle args) {
      if (super.performAccessibilityAction(host, action, args)) {
        return true;
      }
      switch (action) {
        case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
          if ((mOrientation == Orientation.VERTICAL && internalCanScrollVertically(1))
              || (mOrientation == Orientation.HORIZONTAL && canScrollHorizontally(1))) {
            setCurrentItem(mCurItem + 1);
            return true;
          }
        }
        return false;
        case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
          if ((mOrientation == Orientation.VERTICAL && internalCanScrollVertically(-1))
              || (mOrientation == Orientation.HORIZONTAL && canScrollHorizontally(-1))) {
            setCurrentItem(mCurItem - 1);
            return true;
          }
        }
        return false;
      }
      return false;
    }

    private boolean canScroll() {
      return (mAdapter != null) && (mAdapter.getCount() > 1);
    }
  }

  private class PagerObserver extends DataSetObserver {
    @Override
    public void onChanged() {
      dataSetChanged();
    }

    @Override
    public void onInvalidated() {
      dataSetChanged();
    }
  }

  /**
   * Layout parameters that should be supplied for views added to a
   * ViewPager.
   */
  public static class LayoutParams extends ViewGroup.LayoutParams {
    /**
     * true if this view is a decoration on the pager itself and not
     * a view supplied by the adapter.
     */
    public boolean isDecor;

    /**
     * Gravity setting for use on decor views only:
     * Where to position the view page within the overall ViewPager
     * container; constants are defined in {@link android.view.Gravity}.
     */
    public int gravity;

    /**
     * Width as a 0-1 multiplier of the measured pager height
     */
    float heightFactor = 0.f;

    /**
     * Width as a 0-1 multiplier of the measured pager width
     */
    float widthFactor = 0.f;

    /**
     * true if this view was added during layout and needs to be measured
     * before being positioned.
     */
    boolean needsMeasure;

    /**
     * Adapter position this view is for if !isDecor
     */
    int position;

    /**
     * Current child index within the ViewPager that this view occupies
     */
    int childIndex;

    public LayoutParams() {
      super(FILL_PARENT, FILL_PARENT);
    }

    public LayoutParams(Context context, AttributeSet attrs) {
      super(context, attrs);

      final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
      gravity = a.getInteger(0, Gravity.TOP);
      a.recycle();
    }
  }

  static class ViewPositionComparator implements Comparator<View> {
    @Override
    public int compare(View lhs, View rhs) {
      final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
      final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
      if (llp.isDecor != rlp.isDecor) {
        return llp.isDecor ? 1 : -1;
      }
      return llp.position - rlp.position;
    }
  }

  // Following classes and the interface are needed for the Maven Central upload script to work properly.
  // They are being introduced here, sort of temporarily (until I find a better solution for this issue).

  /**
   * Callbacks a {@link Parcelable} creator should implement.
   */
  public interface ParcelableCompatCreatorCallbacks<T> {

    /**
     * Create a new instance of the Parcelable class, instantiating it
     * from the given Parcel whose data had previously been written by
     * {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and
     * using the given ClassLoader.
     *
     * @param in The Parcel to read the object's data from.
     * @param loader The ClassLoader that this object is being created in.
     * @return Returns a new instance of the Parcelable class.
     */
    public T createFromParcel(Parcel in, ClassLoader loader);

    /**
     * Create a new array of the Parcelable class.
     *
     * @param size Size of the array.
     * @return Returns an array of the Parcelable class, with every entry
     * initialized to null.
     */
    public T[] newArray(int size);
  }

  /**
   * Helper for accessing features in {@link android.os.Parcelable}
   * introduced after API level 4 in a backwards compatible fashion.
   */
  public static class ParcelableCompat {

    /**
     * Factory method for {@link Parcelable.Creator}.
     *
     * @param callbacks Creator callbacks implementation.
     * @return New creator.
     */
    public static <T> Parcelable.Creator<T> newCreator(
        OrientedViewPager.ParcelableCompatCreatorCallbacks<T> callbacks) {
      if (android.os.Build.VERSION.SDK_INT >= 13) {
        return ParcelableCompatCreatorHoneycombMR2Stub.instantiate(callbacks);
      }
      return new CompatCreator<T>(callbacks);
    }

    public static class CompatCreator<T> implements Parcelable.Creator<T> {
      final OrientedViewPager.ParcelableCompatCreatorCallbacks<T> mCallbacks;

      public CompatCreator(OrientedViewPager.ParcelableCompatCreatorCallbacks<T> callbacks) {
        mCallbacks = callbacks;
      }

      @Override
      public T createFromParcel(Parcel source) {
        return mCallbacks.createFromParcel(source, null);
      }

      @Override
      public T[] newArray(int size) {
        return mCallbacks.newArray(size);
      }
    }
  }

  static class ParcelableCompatCreatorHoneycombMR2Stub {
    public static <T> Parcelable.Creator<T> instantiate(
        OrientedViewPager.ParcelableCompatCreatorCallbacks<T> callbacks) {
      return new ParcelableCompatCreatorHoneycombMR2<T>(callbacks);
    }
  }

  static class ParcelableCompatCreatorHoneycombMR2<T> implements Parcelable.ClassLoaderCreator<T> {
    private final OrientedViewPager.ParcelableCompatCreatorCallbacks<T> mCallbacks;

    public ParcelableCompatCreatorHoneycombMR2(
        OrientedViewPager.ParcelableCompatCreatorCallbacks<T> callbacks) {
      mCallbacks = callbacks;
    }

    public T createFromParcel(Parcel in) {
      return mCallbacks.createFromParcel(in, null);
    }

    public T createFromParcel(Parcel in, ClassLoader loader) {
      return mCallbacks.createFromParcel(in, loader);
    }

    public T[] newArray(int size) {
      return mCallbacks.newArray(size);
    }
  }
}

================================================
FILE: library/src/main/java/com/gu/library/transformer/VerticalBaseTransformer.java
================================================
package com.gu.library.transformer;

import android.support.v4.view.ViewPager;
import android.view.View;

/**
 * Created by Nate on 2016/7/22.
 */
public abstract class VerticalBaseTransformer implements ViewPager.PageTransformer {
    /**
     * Called each {@link #transformPage(View, float)}.
     *
     * @param page     Apply the transformation to this page
     * @param position Position of page relative to the current front-and-center position of the pager. 0 is front and
     *                 center. 1 is one full page position to the right, and -1 is one page position to the left.
     */
    protected abstract void onTransform(View page, float position);

    /**
     * Apply a property transformation to the given page. For most use cases, this method should not be overridden.
     * Instead use {@link #transformPage(View, float)} to perform typical transformations.
     *
     * @param page     Apply the transformation to this page
     * @param position Position of page relative to the current front-and-center position of the pager. 0 is front and
     *                 center. 1 is one full page position to the right, and -1 is one page position to the left.
     */
    @Override
    public void transformPage(View page, float position) {
        onPreTransform(page, position);
        onTransform(page, position);
        onPostTransform(page, position);
    }

    /**
     * If the position offset of a fragment is less than negative one or greater than one, returning true will set the
     * fragment alpha to 0f. Otherwise fragment alpha is always defaulted to 1f.
     *
     * @return
     */
    protected boolean hideOffscreenPages() {
        return true;
    }

    /**
     * Indicates if the default animations of the view pager should be used.
     *
     * @return
     */
    protected boolean isPagingEnabled() {
        return false;
    }

    /**
     * Called each {@link #transformPage(View, float)} before {{@link #onTransform(View, float)}.
     * <p/>
     * The default implementation attempts to reset all view properties. This is useful when toggling transforms that do
     * not modify the same page properties. For instance changing from a transformation that applies rotation to a
     * transformation that fades can inadvertently leave a fragment stuck with a rotation or with some degree of applied
     * alpha.
     *
     * @param page     Apply the transformation to this page
     * @param position Position of page relative to the current front-and-center position of the pager. 0 is front and
     *                 center. 1 is one full page position to the right, and -1 is one page position to the left.
     */
    protected void onPreTransform(View page, float position) {
        final float width = page.getWidth();
        final float height = page.getHeight();

        page.setRotationX(0);
        page.setRotationY(0);
        page.setRotation(0);
        page.setScaleX(1);
        page.setScaleY(1);
        page.setPivotX(0);
        page.setPivotY(0);
        page.setTranslationX(0);
        page.setTranslationY(isPagingEnabled() ? 0f : -height * position);

        if (hideOffscreenPages()) {
            page.setAlpha(position <= -1f || position >= 1f ? 0f : 1f);
        } else {
            page.setAlpha(1f);
        }

        /*final float normalizedposition = Math.abs(Math.abs(position) - 1);
        page.setAlpha(normalizedposition);*/
    }

    /**
     * Called each {@link #transformPage(View, float)} after {@link #onTransform(View, float)}.
     *
     * @param page     Apply the transformation to this page
     * @param position Position of page relative to the current front-and-center position of the pager. 0 is front and
     *                 center. 1 is one full page position to the right, and -1 is one page position to the left.
     */
    protected void onPostTransform(View page, float position) {
    }

    /**
     * Same as {@link Math#min(double, double)} without double casting, zero closest to infinity handling, or NaN support.
     *
     * @param val
     * @param min
     * @return
     */
    protected static final float min(float val, float min) {
        return val < min ? min : val;
    }
}


================================================
FILE: library/src/main/java/com/gu/library/transformer/VerticalStackTransformer.java
================================================
package com.gu.library.transformer;

import android.content.Context;
import android.util.Log;
import android.view.View;

import com.gu.library.utils.ScreenUtils;


/**
 * Created by Nate on 2016/7/22.
 */
public class VerticalStackTransformer extends VerticalBaseTransformer {

    private Context context;
    private int spaceBetweenFirAndSecWith = 10 * 2;//第一张卡片和第二张卡片宽度差  dp单位
    private int spaceBetweenFirAndSecHeight = 10;//第一张卡片和第二张卡片高度差   dp单位

    public VerticalStackTransformer(Context context) {
        this.context = context;
    }

    public VerticalStackTransformer(Context context, int spaceBetweenFirAndSecWith, int spaceBetweenFirAndSecHeight) {
        this.context = context;
        this.spaceBetweenFirAndSecWith = spaceBetweenFirAndSecWith;
        this.spaceBetweenFirAndSecHeight = spaceBetweenFirAndSecHeight;
    }

    @Override
    protected void onTransform(View page, float position) {
        if (position <= 0.0f) {
            page.setAlpha(1.0f);
            Log.e("onTransform", "position <= 0.0f ==>" + position);
            page.setTranslationY(0f);
            //控制停止滑动切换的时候,只有最上面的一张卡片可以点击
            page.setClickable(true);
        } else if (position <= 3.0f) {
            Log.e("onTransform", "position <= 3.0f ==>" + position);
            float scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, spaceBetweenFirAndSecWith * position)) / (float) (page.getWidth());
            //控制下面卡片的可见度
            page.setAlpha(1.0f);
            //控制停止滑动切换的时候,只有最上面的一张卡片可以点击
            page.setClickable(false);
            page.setPivotX(page.getWidth() / 2f);
            page.setPivotY(page.getHeight() / 2f);
            page.setScaleX(scale);
            page.setScaleY(scale);
            page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, spaceBetweenFirAndSecHeight) * position);
        }
    }
}


================================================
FILE: library/src/main/java/com/gu/library/utils/ScreenUtils.java
================================================
package com.gu.library.utils;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;

/**
 * Created by Nate on 2015/9/10. 屏幕相关工具类,可以获取屏幕宽高度,还有截取屏幕
 */
public class ScreenUtils {

    private ScreenUtils() {
        /* cannot be instantiated */
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    /**
     * 获得屏幕高度
     *
     * @param context
     * @return
     */
    public static int getScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }

    /**
     * 获得屏幕宽度
     *
     * @param context
     * @return
     */
    public static int getScreenHeight(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.heightPixels;
    }

    /**
     * 获得状态栏的高度
     *
     * @param context
     * @return
     */
    public static int getStatusHeight(Context context) {

        int statusHeight = -1;
        try {
            Class<?> clazz = Class.forName("com.android.internal.R$dimen");
            Object object = clazz.newInstance();
            int height = Integer.parseInt(clazz.getField("status_bar_height")
                    .get(object).toString());
            statusHeight = context.getResources().getDimensionPixelSize(height);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return statusHeight;
    }

    /**
     * 获取当前屏幕截图,包含状态栏
     *
     * @param activity
     * @return
     */
    public static Bitmap snapShotWithStatusBar(Activity activity) {
        View view = activity.getWindow().getDecorView();
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        Bitmap bmp = view.getDrawingCache();
        int width = getScreenWidth(activity);
        int height = getScreenHeight(activity);
        Bitmap bp = null;
        bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
        view.destroyDrawingCache();
        return bp;

    }

    /**
     * 获取当前屏幕截图,不包含状态栏
     *
     * @param activity
     * @return
     */
    public static Bitmap snapShotWithoutStatusBar(Activity activity) {
        View view = activity.getWindow().getDecorView();
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        Bitmap bmp = view.getDrawingCache();
        Rect frame = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
        int statusBarHeight = frame.top;
        int width = getScreenWidth(activity);
        int height = getScreenHeight(activity);
        Bitmap bp = null;
        bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height
                - statusBarHeight);
        view.destroyDrawingCache();
        return bp;
    }

    /**
     * dp转px
     *
     * @param context
     * @param dpVal
     * @return
     */
    public static int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }

    /**
     * sp转px
     *
     * @param context
     * @param spVal
     * @return
     */
    public static int sp2px(Context context, float spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, context.getResources().getDisplayMetrics());
    }

    /**
     * px转dp
     *
     * @param context
     * @param pxVal
     * @return
     */
    public static float px2dp(Context context, float pxVal) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (pxVal / scale);
    }

    /**
     * px转sp
     *
     * @param context
     * @param pxVal
     * @return
     */
    public static float px2sp(Context context, float pxVal) {
        return (pxVal / context.getResources().getDisplayMetrics().scaledDensity);
    }

    /**
     * 动态设置图片宽高
     */
    /**
     * 动态设置图片宽高
     */
    public static float[] getBitmapConfiguration(Bitmap bitmap, ImageView imageView, float screenRadio) {
        int screenWidth = getScreenWidth(imageView.getContext());
        float rawWidth = 0;
        float rawHeight = 0;
        float width = 0;
        float height = 0;
        if (bitmap == null) {
            width = (float) (screenWidth / screenRadio);
            height = (float) width;
            imageView.setScaleType(ImageView.ScaleType.FIT_XY);
        } else {
            rawWidth = bitmap.getWidth();
            rawHeight = bitmap.getHeight();
            if (rawHeight > 10 * rawWidth) {
                imageView.setScaleType(ImageView.ScaleType.CENTER);
            } else {
                imageView.setScaleType(ImageView.ScaleType.FIT_XY);
            }
            float radio = rawHeight / rawWidth;
            width = (float) (screenWidth / screenRadio);
            height = (float) (radio * width);
        }
        return new float[]{width, height};
    }
}


================================================
FILE: library/src/test/java/com/gu/library/ExampleUnitTest.java
================================================
package com.gu.library;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * To work on unit tests, switch the Test Artifact in the Build Variants view.
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

================================================
FILE: settings.gradle
================================================
include ':app', ':library'
Download .txt
gitextract_qfkxk7en/

├── .gitignore
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── gu/
│       │               └── cardstackviewpager/
│       │                   └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── gu/
│       │   │           └── cardstackviewpager/
│       │   │               ├── activity/
│       │   │               │   ├── AboutActivity.java
│       │   │               │   ├── HomeActivity.java
│       │   │               │   └── WebViewActivity.java
│       │   │               ├── adapter/
│       │   │               │   └── ContentFragmentAdapter.java
│       │   │               └── fragment/
│       │   │                   └── CardFragment.java
│       │   └── res/
│       │       ├── anim/
│       │       │   ├── slide_left_in.xml
│       │       │   ├── slide_left_out.xml
│       │       │   ├── slide_right_in.xml
│       │       │   └── slide_right_out.xml
│       │       ├── drawable/
│       │       │   ├── back_iv_bg.xml
│       │       │   ├── custom_ll_bg.xml
│       │       │   ├── github_click_bg.xml
│       │       │   └── progress_bar_h5.xml
│       │       ├── layout/
│       │       │   ├── activity_about.xml
│       │       │   ├── activity_home.xml
│       │       │   ├── activity_webview.xml
│       │       │   └── fragment_card.xml
│       │       └── values/
│       │           ├── colors.xml
│       │           ├── dimens.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── gu/
│                       └── cardstackviewpager/
│                           └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── gu/
│       │               └── library/
│       │                   └── ApplicationTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── com/
│       │           └── gu/
│       │               └── library/
│       │                   ├── OrientedViewPager.java
│       │                   ├── transformer/
│       │                   │   ├── VerticalBaseTransformer.java
│       │                   │   └── VerticalStackTransformer.java
│       │                   └── utils/
│       │                       └── ScreenUtils.java
│       └── test/
│           └── java/
│               └── com/
│                   └── gu/
│                       └── library/
│                           └── ExampleUnitTest.java
└── settings.gradle
Download .txt
SYMBOL INDEX (189 symbols across 13 files)

FILE: app/src/androidTest/java/com/gu/cardstackviewpager/ApplicationTest.java
  class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
    method ApplicationTest (line 10) | public ApplicationTest() {

FILE: app/src/main/java/com/gu/cardstackviewpager/activity/AboutActivity.java
  class AboutActivity (line 14) | public class AboutActivity extends AppCompatActivity {
    method onCreate (line 16) | @Override
    method finish (line 54) | @Override

FILE: app/src/main/java/com/gu/cardstackviewpager/activity/HomeActivity.java
  class HomeActivity (line 22) | public class HomeActivity extends AppCompatActivity {
    method onCreate (line 28) | @Override

FILE: app/src/main/java/com/gu/cardstackviewpager/activity/WebViewActivity.java
  class WebViewActivity (line 16) | public class WebViewActivity extends AppCompatActivity {
    method onCreate (line 24) | @Override
    class WebChromeClient (line 52) | public class WebChromeClient extends android.webkit.WebChromeClient {
      method onProgressChanged (line 53) | @Override
    class WebViewClient (line 67) | class WebViewClient extends android.webkit.WebViewClient {
      method shouldOverrideUrlLoading (line 69) | @Override
    method onBackPressed (line 76) | @Override
    method finish (line 86) | @Override

FILE: app/src/main/java/com/gu/cardstackviewpager/adapter/ContentFragmentAdapter.java
  class ContentFragmentAdapter (line 11) | public class ContentFragmentAdapter extends FragmentStatePagerAdapter {
    method ContentFragmentAdapter (line 15) | public ContentFragmentAdapter(FragmentManager fm, List<Fragment> fragm...
    method getItem (line 20) | @Override
    method getCount (line 25) | @Override
    method getPageTitle (line 30) | @Override
    method getItemPosition (line 35) | @Override
    method getItemPosition (line 40) | public int getItemPosition() {
    method setItemPosition (line 43) | public void setItemPosition(int itemPosition) {

FILE: app/src/main/java/com/gu/cardstackviewpager/fragment/CardFragment.java
  class CardFragment (line 18) | public class CardFragment extends Fragment {
    method newInstance (line 21) | public static CardFragment newInstance(int index) {
    method onCreateView (line 29) | @Override

FILE: app/src/test/java/com/gu/cardstackviewpager/ExampleUnitTest.java
  class ExampleUnitTest (line 10) | public class ExampleUnitTest {
    method addition_isCorrect (line 11) | @Test

FILE: library/src/androidTest/java/com/gu/library/ApplicationTest.java
  class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
    method ApplicationTest (line 10) | public ApplicationTest() {

FILE: library/src/main/java/com/gu/library/OrientedViewPager.java
  class OrientedViewPager (line 68) | public class OrientedViewPager extends ViewGroup {
    type Orientation (line 70) | public enum Orientation {
    class ItemInfo (line 97) | private static class ItemInfo {
    method compare (line 106) | @Override
    method getInterpolation (line 113) | public float getInterpolation(float t) {
    method run (line 233) | public void run() {
    type OnAdapterChangeListener (line 244) | interface OnAdapterChangeListener {
      method onAdapterChanged (line 245) | public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter n...
    type Decor (line 252) | interface Decor {
    method OrientedViewPager (line 255) | public OrientedViewPager(Context context) {
    method OrientedViewPager (line 260) | public OrientedViewPager(Context context, AttributeSet attrs) {
    method initViewPager (line 265) | void initViewPager() {
    method setOrientation (line 293) | public void setOrientation(Orientation orientation) {
    method onDetachedFromWindow (line 297) | @Override
    method setScrollState (line 303) | private void setScrollState(int newState) {
    method setAdapter (line 323) | public void setAdapter(PagerAdapter adapter) {
    method removeNonDecorViews (line 369) | private void removeNonDecorViews() {
    method getAdapter (line 385) | public PagerAdapter getAdapter() {
    method setOnAdapterChangeListener (line 389) | void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
    method getClientSize (line 393) | private int getClientSize() {
    method setCurrentItem (line 406) | public void setCurrentItem(int item) {
    method setCurrentItem (line 417) | public void setCurrentItem(int item, boolean smoothScroll) {
    method getCurrentItem (line 422) | public int getCurrentItem() {
    method setCurrentItemInternal (line 426) | void setCurrentItemInternal(int item, boolean smoothScroll, boolean al...
    method setCurrentItemInternal (line 430) | void setCurrentItemInternal(int item, boolean smoothScroll, boolean al...
    method scrollToItem (line 473) | private void scrollToItem(int item, boolean smoothScroll, int velocity,
    method setOnPageChangeListener (line 517) | public void setOnPageChangeListener(ViewPager.OnPageChangeListener lis...
    method setPageTransformer (line 534) | public void setPageTransformer(boolean reverseDrawingOrder,
    method setChildrenDrawingOrderEnabledCompat (line 550) | void setChildrenDrawingOrderEnabledCompat(boolean enable) {
    method getChildDrawingOrder (line 568) | @Override
    method setInternalPageChangeListener (line 582) | ViewPager.OnPageChangeListener setInternalPageChangeListener(
    method getOffscreenPageLimit (line 596) | public int getOffscreenPageLimit() {
    method setOffscreenPageLimit (line 617) | public void setOffscreenPageLimit(int limit) {
    method setPageMargin (line 637) | public void setPageMargin(int marginPixels) {
    method getPageMargin (line 652) | public int getPageMargin() {
    method setPageMarginDrawable (line 661) | public void setPageMarginDrawable(Drawable d) {
    method setPageMarginDrawable (line 673) | public void setPageMarginDrawable(int resId) {
    method verifyDrawable (line 677) | @Override
    method drawableStateChanged (line 682) | @Override
    method distanceInfluenceForSnapDuration (line 695) | float distanceInfluenceForSnapDuration(float f) {
    method smoothScrollTo (line 707) | void smoothScrollTo(int x, int y) {
    method smoothScrollTo (line 718) | void smoothScrollTo(int x, int y, int velocity) {
    method addNewItem (line 759) | ItemInfo addNewItem(int position, int index) {
    method dataSetChanged (line 772) | void dataSetChanged() {
    method populate (line 843) | void populate() {
    method populate (line 847) | void populate(int newCurrentItem) {
    method sortChildDrawingOrder (line 1052) | private void sortChildDrawingOrder() {
    method calculatePageOffsets (line 1068) | private void calculatePageOffsets(ItemInfo curItem, int curIndex, Item...
    class ViewPagerSavedState (line 1160) | public static class ViewPagerSavedState extends BaseSavedState {
      method ViewPagerSavedState (line 1165) | public ViewPagerSavedState(Parcelable superState) {
      method writeToParcel (line 1169) | @Override
      method toString (line 1176) | @Override
      method createFromParcel (line 1185) | @Override
      method newArray (line 1190) | @Override
      method ViewPagerSavedState (line 1196) | ViewPagerSavedState(Parcel in, ClassLoader loader) {
    method onSaveInstanceState (line 1207) | @Override
    method onRestoreInstanceState (line 1218) | @Override
    method addView (line 1238) | @Override
    method removeView (line 1264) | @Override
    method infoForChild (line 1273) | ItemInfo infoForChild(View child) {
    method infoForAnyChild (line 1283) | ItemInfo infoForAnyChild(View child) {
    method infoForPosition (line 1294) | ItemInfo infoForPosition(int position) {
    method onAttachedToWindow (line 1304) | @Override
    method onMeasure (line 1310) | @Override
    method onSizeChanged (line 1423) | @Override
    method recomputeScrollPosition (line 1439) | private void recomputeScrollPosition(int size, int oldSize, int margin...
    method onLayout (line 1497) | @Override
    method computeScroll (line 1634) | @Override
    method pageScrolled (line 1666) | private boolean pageScrolled(int pos) {
    method onPageScrolled (line 1705) | protected void onPageScrolled(int position, float offset, int offsetPi...
    method completeScroll (line 1811) | private void completeScroll(boolean postEvents) {
    method isGutterDrag (line 1842) | private boolean isGutterDrag(float axis, float dAxis) {
    method enableLayers (line 1848) | private void enableLayers(boolean enable) {
    method onInterceptTouchEvent (line 1857) | @Override
    method onTouchEvent (line 2064) | @Override
    method requestParentDisallowInterceptTouchEvent (line 2249) | private void requestParentDisallowInterceptTouchEvent(boolean disallow...
    method performDrag (line 2256) | private boolean performDrag(float dimen) {
    method infoForCurrentScrollPosition (line 2353) | private ItemInfo infoForCurrentScrollPosition() {
    method determineTargetPage (line 2397) | private int determineTargetPage(int currentPage, float pageOffset, int...
    method draw (line 2417) | @Override
    method onDraw (line 2483) | @Override
    method beginFakeDrag (line 2581) | public boolean beginFakeDrag() {
    method endFakeDrag (line 2611) | public void endFakeDrag() {
    method fakeDragBy (line 2658) | public void fakeDragBy(float offset) {
    method isFakeDragging (line 2744) | public boolean isFakeDragging() {
    method onSecondaryPointerUp (line 2748) | private void onSecondaryPointerUp(MotionEvent ev) {
    method endDrag (line 2767) | private void endDrag() {
    method setScrollingCacheEnabled (line 2777) | private void setScrollingCacheEnabled(boolean enabled) {
    method internalCanScrollVertically (line 2792) | public boolean internalCanScrollVertically(int direction) {
    method canScroll (line 2819) | protected boolean canScroll(View v, boolean checkV, int delta, int x, ...
    method dispatchKeyEvent (line 2851) | @Override
    method executeKeyEvent (line 2865) | public boolean executeKeyEvent(KeyEvent event) {
    method arrowScroll (line 2891) | public boolean arrowScroll(int direction) {
    method getChildRectInPagerCoordinates (line 2977) | private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
    method pageBack (line 3003) | boolean pageBack() {
    method pageForward (line 3011) | boolean pageForward() {
    method addFocusables (line 3022) | @Override
    method addTouchables (line 3066) | @Override
    method onRequestFocusInDescendants (line 3085) | @Override
    method dispatchPopulateAccessibilityEvent (line 3115) | @Override
    method generateDefaultLayoutParams (line 3138) | @Override
    method generateLayoutParams (line 3143) | @Override
    method checkLayoutParams (line 3148) | @Override
    method generateLayoutParams (line 3153) | @Override
    class MyAccessibilityDelegate (line 3158) | class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
      method onInitializeAccessibilityEvent (line 3160) | @Override
      method onInitializeAccessibilityNodeInfo (line 3174) | @Override
      method performAccessibilityAction (line 3196) | @Override
      method canScroll (line 3222) | private boolean canScroll() {
    class PagerObserver (line 3227) | private class PagerObserver extends DataSetObserver {
      method onChanged (line 3228) | @Override
      method onInvalidated (line 3233) | @Override
    class LayoutParams (line 3243) | public static class LayoutParams extends ViewGroup.LayoutParams {
      method LayoutParams (line 3283) | public LayoutParams() {
      method LayoutParams (line 3287) | public LayoutParams(Context context, AttributeSet attrs) {
    class ViewPositionComparator (line 3296) | static class ViewPositionComparator implements Comparator<View> {
      method compare (line 3297) | @Override
    type ParcelableCompatCreatorCallbacks (line 3314) | public interface ParcelableCompatCreatorCallbacks<T> {
      method createFromParcel (line 3326) | public T createFromParcel(Parcel in, ClassLoader loader);
      method newArray (line 3335) | public T[] newArray(int size);
    class ParcelableCompat (line 3342) | public static class ParcelableCompat {
      method newCreator (line 3350) | public static <T> Parcelable.Creator<T> newCreator(
      class CompatCreator (line 3358) | public static class CompatCreator<T> implements Parcelable.Creator<T> {
        method CompatCreator (line 3361) | public CompatCreator(OrientedViewPager.ParcelableCompatCreatorCall...
        method createFromParcel (line 3365) | @Override
        method newArray (line 3370) | @Override
    class ParcelableCompatCreatorHoneycombMR2Stub (line 3377) | static class ParcelableCompatCreatorHoneycombMR2Stub {
      method instantiate (line 3378) | public static <T> Parcelable.Creator<T> instantiate(
    class ParcelableCompatCreatorHoneycombMR2 (line 3384) | static class ParcelableCompatCreatorHoneycombMR2<T> implements Parcela...
      method ParcelableCompatCreatorHoneycombMR2 (line 3387) | public ParcelableCompatCreatorHoneycombMR2(
      method createFromParcel (line 3392) | public T createFromParcel(Parcel in) {
      method createFromParcel (line 3396) | public T createFromParcel(Parcel in, ClassLoader loader) {
      method newArray (line 3400) | public T[] newArray(int size) {

FILE: library/src/main/java/com/gu/library/transformer/VerticalBaseTransformer.java
  class VerticalBaseTransformer (line 9) | public abstract class VerticalBaseTransformer implements ViewPager.PageT...
    method onTransform (line 17) | protected abstract void onTransform(View page, float position);
    method transformPage (line 27) | @Override
    method hideOffscreenPages (line 40) | protected boolean hideOffscreenPages() {
    method isPagingEnabled (line 49) | protected boolean isPagingEnabled() {
    method onPreTransform (line 65) | protected void onPreTransform(View page, float position) {
    method onPostTransform (line 96) | protected void onPostTransform(View page, float position) {
    method min (line 106) | protected static final float min(float val, float min) {

FILE: library/src/main/java/com/gu/library/transformer/VerticalStackTransformer.java
  class VerticalStackTransformer (line 13) | public class VerticalStackTransformer extends VerticalBaseTransformer {
    method VerticalStackTransformer (line 19) | public VerticalStackTransformer(Context context) {
    method VerticalStackTransformer (line 23) | public VerticalStackTransformer(Context context, int spaceBetweenFirAn...
    method onTransform (line 29) | @Override

FILE: library/src/main/java/com/gu/library/utils/ScreenUtils.java
  class ScreenUtils (line 16) | public class ScreenUtils {
    method ScreenUtils (line 18) | private ScreenUtils() {
    method getScreenWidth (line 29) | public static int getScreenWidth(Context context) {
    method getScreenHeight (line 43) | public static int getScreenHeight(Context context) {
    method getStatusHeight (line 57) | public static int getStatusHeight(Context context) {
    method snapShotWithStatusBar (line 78) | public static Bitmap snapShotWithStatusBar(Activity activity) {
    method snapShotWithoutStatusBar (line 98) | public static Bitmap snapShotWithoutStatusBar(Activity activity) {
    method dp2px (line 122) | public static int dp2px(Context context, float dpVal) {
    method sp2px (line 134) | public static int sp2px(Context context, float spVal) {
    method px2dp (line 146) | public static float px2dp(Context context, float pxVal) {
    method px2sp (line 158) | public static float px2sp(Context context, float pxVal) {
    method getBitmapConfiguration (line 168) | public static float[] getBitmapConfiguration(Bitmap bitmap, ImageView ...

FILE: library/src/test/java/com/gu/library/ExampleUnitTest.java
  class ExampleUnitTest (line 10) | public class ExampleUnitTest {
    method addition_isCorrect (line 11) | @Test
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (192K chars).
[
  {
    "path": ".gitignore",
    "chars": 114,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n/app/build\n.idea\n"
  },
  {
    "path": "README.md",
    "chars": 8665,
    "preview": "# CardStackViewpager 卡片翻页效果的Viewpager\n\n这个`CardStackViewpager `的灵感来自Github上面的\t[`FlippableStackView`](https://github.com/b"
  },
  {
    "path": "app/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 748,
    "preview": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 27\n    buildToolsVersion \"28.0.2\"\n\n    defaultC"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 650,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in D:"
  },
  {
    "path": "app/src/androidTest/java/com/gu/cardstackviewpager/ApplicationTest.java",
    "chars": 356,
    "preview": "package com.gu.cardstackviewpager;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a "
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 1509,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package="
  },
  {
    "path": "app/src/main/java/com/gu/cardstackviewpager/activity/AboutActivity.java",
    "chars": 1966,
    "preview": "package com.gu.cardstackviewpager.activity;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.sup"
  },
  {
    "path": "app/src/main/java/com/gu/cardstackviewpager/activity/HomeActivity.java",
    "chars": 2042,
    "preview": "package com.gu.cardstackviewpager.activity;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.sup"
  },
  {
    "path": "app/src/main/java/com/gu/cardstackviewpager/activity/WebViewActivity.java",
    "chars": 2863,
    "preview": "package com.gu.cardstackviewpager.activity;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimpor"
  },
  {
    "path": "app/src/main/java/com/gu/cardstackviewpager/adapter/ContentFragmentAdapter.java",
    "chars": 1180,
    "preview": "package com.gu.cardstackviewpager.adapter;\n\nimport android.support.v4.app.Fragment;\nimport android.support.v4.app.Fragme"
  },
  {
    "path": "app/src/main/java/com/gu/cardstackviewpager/fragment/CardFragment.java",
    "chars": 1484,
    "preview": "package com.gu.cardstackviewpager.fragment;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimpor"
  },
  {
    "path": "app/src/main/res/anim/slide_left_in.xml",
    "chars": 207,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate"
  },
  {
    "path": "app/src/main/res/anim/slide_left_out.xml",
    "chars": 207,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate"
  },
  {
    "path": "app/src/main/res/anim/slide_right_in.xml",
    "chars": 206,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate"
  },
  {
    "path": "app/src/main/res/anim/slide_right_out.xml",
    "chars": 206,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate"
  },
  {
    "path": "app/src/main/res/drawable/back_iv_bg.xml",
    "chars": 262,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item a"
  },
  {
    "path": "app/src/main/res/drawable/custom_ll_bg.xml",
    "chars": 364,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item a"
  },
  {
    "path": "app/src/main/res/drawable/github_click_bg.xml",
    "chars": 367,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item a"
  },
  {
    "path": "app/src/main/res/drawable/progress_bar_h5.xml",
    "chars": 580,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <it"
  },
  {
    "path": "app/src/main/res/layout/activity_about.xml",
    "chars": 6979,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        "
  },
  {
    "path": "app/src/main/res/layout/activity_home.xml",
    "chars": 1480,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/activity_webview.xml",
    "chars": 934,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        "
  },
  {
    "path": "app/src/main/res/layout/fragment_card.xml",
    "chars": 1291,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- 使用AndroidAutoLayout进行卡片适配 -->\n<LinearLayout xmlns:android=\"http://schemas.and"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 376,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"green\">#00BA9C</color>\n    <color name=\"green_dark\">"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 1124,
    "preview": "<resources>\n    <!-- 字体大小 -->\n    <dimen name=\"text_font_size_big_60sp\">60sp</dimen>\n    <dimen name=\"text_font_size_big"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 357,
    "preview": "<resources>\n  <string name=\"app_name\">CardStackViewpager</string>\n  <string name=\"my_name\">Nate Robinson</string>\n  <str"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 620,
    "preview": "<resources>\n\n  <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n    <item name=\"colorPrimary\">@color/g"
  },
  {
    "path": "app/src/test/java/com/gu/cardstackviewpager/ExampleUnitTest.java",
    "chars": 318,
    "preview": "package com.gu.cardstackviewpager;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * To work on unit te"
  },
  {
    "path": "build.gradle",
    "chars": 546,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n  repo"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 233,
    "preview": "#Mon Dec 28 10:00:20 PST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 855,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 4971,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2314,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
  },
  {
    "path": "library/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "library/build.gradle",
    "chars": 593,
    "preview": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 27\n    buildToolsVersion \"28.0.2\"\n\n    defaultConfi"
  },
  {
    "path": "library/proguard-rules.pro",
    "chars": 650,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in D:"
  },
  {
    "path": "library/src/androidTest/java/com/gu/library/ApplicationTest.java",
    "chars": 345,
    "preview": "package com.gu.library;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http:"
  },
  {
    "path": "library/src/main/AndroidManifest.xml",
    "chars": 48,
    "preview": "<manifest package=\"com.gu.library\">\n</manifest>\n"
  },
  {
    "path": "library/src/main/java/com/gu/library/OrientedViewPager.java",
    "chars": 118940,
    "preview": "/**\n * Copyright 2015 Bartosz Lipinski\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may "
  },
  {
    "path": "library/src/main/java/com/gu/library/transformer/VerticalBaseTransformer.java",
    "chars": 4237,
    "preview": "package com.gu.library.transformer;\n\nimport android.support.v4.view.ViewPager;\nimport android.view.View;\n\n/**\n * Created"
  },
  {
    "path": "library/src/main/java/com/gu/library/transformer/VerticalStackTransformer.java",
    "chars": 1928,
    "preview": "package com.gu.library.transformer;\n\nimport android.content.Context;\nimport android.util.Log;\nimport android.view.View;\n"
  },
  {
    "path": "library/src/main/java/com/gu/library/utils/ScreenUtils.java",
    "chars": 5518,
    "preview": "package com.gu.library.utils;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.Bitm"
  },
  {
    "path": "library/src/test/java/com/gu/library/ExampleUnitTest.java",
    "chars": 307,
    "preview": "package com.gu.library;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * To work on unit tests, switch"
  },
  {
    "path": "settings.gradle",
    "chars": 27,
    "preview": "include ':app', ':library'\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the NateRobinson/CardStackViewpager GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (174.8 KB), approximately 44.8k tokens, and a symbol index with 189 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!