├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── moe │ │ └── codeest │ │ └── ecardflowdemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── moe │ │ │ └── codeest │ │ │ └── ecardflowdemo │ │ │ ├── activity │ │ │ ├── CardFlowActivity.java │ │ │ ├── CardFlowLayoutActivity.java │ │ │ └── MainActivity.java │ │ │ ├── adapter │ │ │ ├── ImageAdapter.java │ │ │ ├── ItemAdapter.java │ │ │ └── PagerAdapter.java │ │ │ ├── fragment │ │ │ ├── ImageFragment.java │ │ │ └── PagerFragment.java │ │ │ └── support │ │ │ └── ZoomOutPageTransformer.java │ └── res │ │ ├── drawable │ │ ├── shape_bg.xml │ │ ├── shape_btn.xml │ │ ├── shape_circle.xml │ │ └── shape_round_rect.xml │ │ ├── layout │ │ ├── activity_cardflow.xml │ │ ├── activity_cardflow_layout.xml │ │ ├── activity_main.xml │ │ ├── fragment_pager.xml │ │ ├── image_pager.xml │ │ └── item_view.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── back.png │ │ ├── cover.jpg │ │ ├── face.jpg │ │ ├── ic_launcher.png │ │ ├── img1.jpg │ │ ├── img2.jpg │ │ └── img3.jpg │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── moe │ └── codeest │ └── ecardflowdemo │ └── ExampleUnitTest.java ├── build.gradle ├── ecardflow ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── moe │ │ └── codeest │ │ └── ecardflow │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── moe │ │ │ └── codeest │ │ │ └── ecardflow │ │ │ ├── ECardFlow.java │ │ │ ├── ECardFlowLayout.java │ │ │ ├── mode │ │ │ ├── AnimMode.java │ │ │ ├── BlurAnimMode.java │ │ │ ├── CrossMoveAnimMode.java │ │ │ ├── DefaultAnimMode.java │ │ │ ├── MoveAnimMode.java │ │ │ └── ScaleAnimMode.java │ │ │ ├── provider │ │ │ ├── DrawableImageProvider.java │ │ │ ├── FileImageProvider.java │ │ │ ├── ImageProvider.java │ │ │ └── PathImageProvider.java │ │ │ ├── support │ │ │ ├── CardFlowTransformer.java │ │ │ └── RecyclingBitmapDrawable.java │ │ │ └── util │ │ │ ├── DimenUtils.java │ │ │ ├── FastBlur.java │ │ │ └── RSBlur.java │ └── res │ │ └── values │ │ ├── attrs.xml │ │ └── strings.xml │ └── test │ └── java │ └── moe │ └── codeest │ └── ecardflow │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── preview ├── GIF.gif ├── GIFBlur.gif └── GIFMove.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Est 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECardFlow 2 | 3 | [![](https://jitpack.io/v/codeestX/ECardFlow.svg)](https://jitpack.io/#codeestX/ECardFlow) 4 | 5 | ECardFlow, A custom ViewPager for multiple card flow system.designed by [Leo Leung](https://ios.uplabs.com/posts/multiple-card-flow 6 | ) 7 | ECardFlow, 一个用于复数卡片滑动与展开效果的ViewPager控件,设计原型出自[Leo Leung](https://ios.uplabs.com/posts/multiple-card-flow 8 | ) 9 | 10 | ECardFlowLayout, A layout provide beautiful background effect for ViewPager. 11 | ECardFlowLayout, 一个为ViewPager提供多种联动背景效果的布局 12 | 13 | # Preview 14 | ECardFlow: 15 | 16 | ![](https://github.com/codeestX/ECardFlow/raw/master/preview/GIF.gif) 17 | 18 | ECardFlowLayout: 19 | 20 | ![](https://github.com/codeestX/ECardFlow/raw/master/preview/GIFBlur.gif) 21 | ![](https://github.com/codeestX/ECardFlow/raw/master/preview/GIFMove.gif) 22 | 23 | # Usage(ECardFlow) 24 | 25 | Step 1. Add the JitPack repository to your build file 26 | 27 | allprojects { 28 | repositories { 29 | ... 30 | maven { url "https://jitpack.io" } 31 | } 32 | } 33 | 34 | Step 2. Add the dependency 35 | 36 | dependencies { 37 | compile 'com.github.codeestX:ECardFlow:v1.0.5' 38 | } 39 | 40 | Step 3. Just use it as a ViewPager 41 | 42 | 51 | 52 | * Only support fragment as each page 53 | * For best performance, giving ECardFlow's parent view an attr`android:clipChildren="false"`,and do `NOT` set ECardFlow's width as`match_parent` 54 | * See DemoProject for more details 55 | 56 | Attrs: 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | Java Code: 82 | 83 | mEcardflow.gotoNext(); 84 | 85 | mEcardflow.gotoLast(); 86 | 87 | mEcardflow.expand(); 88 | 89 | mEcardflow.shrink(); 90 | 91 | mEcardflow.isExpanding(); 92 | 93 | mEcardflow.setTouchSlop(slop); 94 | 95 | mEcardflow.setExpandTime(time); 96 | 97 | mEcardflow.setOnExpandStateListener(new ECardFlow.OnExpandStateListener() { 98 | @Override 99 | public void onExpand(View page, int position) { 100 | 101 | } 102 | 103 | @Override 104 | public void onShrink(View page, int position) { 105 | 106 | } 107 | }); 108 | 109 | # Usage(ECardFlowLayout) 110 | Step 1. Add the JitPack repository to your build file 111 | 112 | allprojects { 113 | repositories { 114 | ... 115 | maven { url "https://jitpack.io" } 116 | } 117 | } 118 | 119 | Step 2. Add the dependency 120 | 121 | dependencies { 122 | compile 'com.github.codeestX:ECardFlow:v1.0.5' 123 | } 124 | 125 | Step 3. Just put a ViewPager in ECardFlowLayout 126 | 127 | 133 | 139 | 140 | 141 | Step 4. setAnimMode & setImageProvider 142 | 143 | mVPLayout = (ECardFlowLayout) findViewById(R.id.ecardflow_layout); 144 | mVPLayout.setAnimMode(new BlurAnimMode()); 145 | mVPLayout.setImageProvider(new DrawableImageProvider(this, res, width, height); 146 | 147 | * Set AnimMode before Set ImageProvider 148 | * Use `mVPLayout.onDestroy();` to release source 149 | * See DemoProject for more details 150 | 151 | Attrs: 152 | 153 | 154 | 155 | 156 | 157 | 158 | Java Code: 159 | 160 | mVPLayout.setImageProvider(imageProvider); 161 | 162 | mVPLayout.setAnimMode(animMode); 163 | 164 | mVPLayout.setSwitchAnimTime(time); 165 | 166 | //maxSize = Runtime.getRuntime().maxMemory(); 167 | //default: maxSize / 5 168 | //min: maxSize / 8 169 | mVPLayout.setCacheSize(megabytes); 170 | 171 | mVPLayout.onDestroy(); 172 | 173 | 174 | | AnimMode | Des | 175 | |:-------------:|:-------------:| 176 | | DefaultAnimMode | No Effect | 177 | | BlurAnimMode | Blur Effect | 178 | | MoveAnimMode | Parallax Effect | 179 | | ScaleAnimMode | Scale Effect | 180 | | CrossMoveAnimMode | Cross Parallax Effect | 181 | 182 | You can custom AnimMode with class which implements `AnimMode` 183 | 184 | | ImageProvider | Des | 185 | |:-------------:|:-------------:| 186 | | DrawableImageProvider | Prividing resId(R.drawable/mipmap.xx) as image resource | 187 | | PathImageProvider | Prividing file path as image resource | 188 | | FileImageProvider | Prividing `File` as image resource | 189 | 190 | You can custom ImageProvider with class which implements `ImageProvider` 191 | 192 | If you choose `BlurAnimMode`, you can enable `RenderScript` for a high performance. 193 | > Open the build.gradle file in the app folder of your application module. 194 | > Add the following RenderScript settings to the file: 195 | > 196 | > android { 197 | > compileSdkVersion 23 198 | > buildToolsVersion "23.0.3" 199 | > 200 | > defaultConfig { 201 | > minSdkVersion 9 202 | > targetSdkVersion 19 203 | > 204 | > //Enable RenderScript 205 | > renderscriptTargetApi 23 206 | > renderscriptSupportModeEnabled true 207 | > } 208 | > } 209 | > 210 | 211 | # [Download Demo APK](https://fir.im/yl3e) 212 | 213 | # License 214 | 215 | MIT Copyright (c) 2017 codeestX 216 | 217 | 218 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "moe.codeest.ecardflowdemo" 8 | minSdkVersion 21 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | 14 | renderscriptTargetApi 23 15 | renderscriptSupportModeEnabled true 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(include: ['*.jar'], dir: 'libs') 27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 28 | exclude group: 'com.android.support', module: 'support-annotations' 29 | }) 30 | compile 'com.android.support:appcompat-v7:25.1.0' 31 | compile 'com.android.support:cardview-v7:25.1.0' 32 | compile 'com.android.support:design:25.1.0' 33 | compile 'jp.wasabeef:recyclerview-animators:2.2.5' 34 | testCompile 'junit:junit:4.12' 35 | compile project(':ecardflow') 36 | } 37 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/chenyulong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/moe/codeest/ecardflowdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("moe.codeest.ecardflowdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/moe/codeest/ecardflowdemo/activity/CardFlowActivity.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.Toolbar; 6 | import android.view.View; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import moe.codeest.ecardflow.ECardFlow; 12 | import moe.codeest.ecardflowdemo.adapter.PagerAdapter; 13 | import moe.codeest.ecardflowdemo.fragment.PagerFragment; 14 | import moe.codeest.ecardflowdemo.R; 15 | 16 | /** 17 | * Created by codeest on 2017/1/21. 18 | */ 19 | 20 | public class CardFlowActivity extends AppCompatActivity implements PagerFragment.OnPageListener { 21 | 22 | private List mList = new ArrayList<>(); 23 | private PagerAdapter mAdapter; 24 | private Toolbar mToolbar; 25 | private ECardFlow mEcardflow; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_cardflow); 31 | init(); 32 | } 33 | 34 | private void init() { 35 | mEcardflow = (ECardFlow) findViewById(R.id.ecardflow); 36 | mToolbar = (Toolbar) findViewById(R.id.toolbar); 37 | 38 | mList.add(PagerFragment.newInstance()); 39 | mList.add(PagerFragment.newInstance()); 40 | mList.add(PagerFragment.newInstance()); 41 | mList.add(PagerFragment.newInstance()); 42 | mList.add(PagerFragment.newInstance()); 43 | 44 | mAdapter = new PagerAdapter(getSupportFragmentManager(), mList); 45 | mEcardflow.setAdapter(mAdapter); 46 | mEcardflow.setOnExpandStateListener(new ECardFlow.OnExpandStateListener() { 47 | @Override 48 | public void onExpand(View page, int position) { 49 | mList.get(position).loadMoreData(); 50 | } 51 | 52 | @Override 53 | public void onShrink(View page, int position) { 54 | mList.get(position).hideMoreData(); 55 | } 56 | }); 57 | 58 | setSupportActionBar(mToolbar); 59 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 60 | getSupportActionBar().setDisplayShowHomeEnabled(true); 61 | mToolbar.setNavigationOnClickListener(new View.OnClickListener() { 62 | @Override 63 | public void onClick(View v) { 64 | onBackPressed(); 65 | } 66 | }); 67 | } 68 | 69 | @Override 70 | public void onShrink() { 71 | mEcardflow.shrink(); 72 | } 73 | 74 | @Override 75 | public void onBackPressed() { 76 | if (mEcardflow.isExpanding()) { 77 | mEcardflow.shrink(); 78 | } else { 79 | super.onBackPressed(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/moe/codeest/ecardflowdemo/activity/CardFlowLayoutActivity.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.view.ViewPager; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import moe.codeest.ecardflow.ECardFlowLayout; 11 | import moe.codeest.ecardflow.mode.BlurAnimMode; 12 | import moe.codeest.ecardflow.mode.CrossMoveAnimMode; 13 | import moe.codeest.ecardflow.mode.DefaultAnimMode; 14 | import moe.codeest.ecardflow.mode.MoveAnimMode; 15 | import moe.codeest.ecardflow.mode.ScaleAnimMode; 16 | import moe.codeest.ecardflow.provider.DrawableImageProvider; 17 | import moe.codeest.ecardflow.util.DimenUtils; 18 | import moe.codeest.ecardflowdemo.R; 19 | import moe.codeest.ecardflowdemo.adapter.ImageAdapter; 20 | import moe.codeest.ecardflowdemo.fragment.ImageFragment; 21 | import moe.codeest.ecardflowdemo.support.ZoomOutPageTransformer; 22 | 23 | /** 24 | * Created by codeest on 2017/1/17. 25 | */ 26 | 27 | public class CardFlowLayoutActivity extends AppCompatActivity { 28 | 29 | public final static int MODE_DEFAULT = 0; 30 | public final static int MODE_BLUR = 1; 31 | public final static int MODE_MOVE = 2; 32 | public final static int MODE_SCALE = 3; 33 | public final static int MODE_CROSS_MOVE = 4; 34 | public final static String ANIM_MODE = "mode"; 35 | 36 | private ECardFlowLayout mVPLayout; 37 | private ViewPager mViewPager; 38 | private ImageAdapter mAdapter; 39 | private List mList = new ArrayList<>(); 40 | private String[] str = {"Pixiv","点兔","宮坂","君名"}; 41 | private int[] res = {R.mipmap.cover, R.mipmap.img1, R.mipmap.img2, R.mipmap.img3}; 42 | private int mMode; 43 | 44 | @Override 45 | protected void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | setContentView(R.layout.activity_cardflow_layout); 48 | mMode = getIntent().getIntExtra(ANIM_MODE, MODE_DEFAULT); 49 | init(); 50 | } 51 | 52 | private void init() { 53 | mVPLayout = (ECardFlowLayout) findViewById(R.id.ecardflow_layout); 54 | mViewPager = (ViewPager) findViewById(R.id.viewpager); 55 | 56 | switch (mMode) { 57 | case 0: 58 | mVPLayout.setAnimMode(new DefaultAnimMode()); 59 | break; 60 | case 1: 61 | mVPLayout.setAnimMode(new BlurAnimMode()); 62 | break; 63 | case 2: 64 | mVPLayout.setAnimMode(new MoveAnimMode()); 65 | break; 66 | case 3: 67 | mVPLayout.setAnimMode(new ScaleAnimMode()); 68 | break; 69 | case 4: 70 | mVPLayout.setAnimMode(new CrossMoveAnimMode()); 71 | break; 72 | } 73 | mVPLayout.setImageProvider(new DrawableImageProvider(this, res, DimenUtils.getScreenWidth(getApplicationContext()), DimenUtils.getScreenHeight(getApplicationContext()))); 74 | for (int i = 0; i < 4; i++) { 75 | mList.add(ImageFragment.newInstance(str[i], res[i])); 76 | } 77 | 78 | mAdapter = new ImageAdapter(getSupportFragmentManager(), mList); 79 | mViewPager.setOffscreenPageLimit(3); 80 | mViewPager.setPageTransformer(true, new ZoomOutPageTransformer()); 81 | mViewPager.setAdapter(mAdapter); 82 | } 83 | 84 | @Override 85 | protected void onDestroy() { 86 | super.onDestroy(); 87 | mVPLayout.onDestroy(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/moe/codeest/ecardflowdemo/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.AppCompatButton; 7 | import android.view.View; 8 | 9 | import moe.codeest.ecardflowdemo.R; 10 | 11 | import static moe.codeest.ecardflowdemo.activity.CardFlowLayoutActivity.ANIM_MODE; 12 | 13 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 14 | 15 | AppCompatButton btnCard, btnLayout, btnLayoutBlur, btnLayoutMove, btnLayoutScale, btnLayoutCrossMove; 16 | 17 | @Override 18 | public void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | 22 | btnCard = (AppCompatButton) findViewById(R.id.btn_card); 23 | btnLayout = (AppCompatButton) findViewById(R.id.btn_layout); 24 | btnLayoutBlur = (AppCompatButton) findViewById(R.id.btn_layout_blur); 25 | btnLayoutMove = (AppCompatButton) findViewById(R.id.btn_layout_move); 26 | btnLayoutScale = (AppCompatButton) findViewById(R.id.btn_layout_scale); 27 | btnLayoutCrossMove = (AppCompatButton) findViewById(R.id.btn_layout_cross); 28 | 29 | btnCard.setOnClickListener(this); 30 | btnLayout.setOnClickListener(this); 31 | btnLayoutBlur.setOnClickListener(this); 32 | btnLayoutMove.setOnClickListener(this); 33 | btnLayoutScale.setOnClickListener(this); 34 | btnLayoutCrossMove.setOnClickListener(this); 35 | } 36 | 37 | @Override 38 | public void onClick(View v) { 39 | if (v == btnCard) { 40 | Intent it = new Intent(this, CardFlowActivity.class); 41 | startActivity(it); 42 | } else if (v == btnLayout) { 43 | Intent it = new Intent(this, CardFlowLayoutActivity.class); 44 | it.putExtra(CardFlowLayoutActivity.ANIM_MODE, CardFlowLayoutActivity.MODE_DEFAULT); 45 | startActivity(it); 46 | } else if (v == btnLayoutBlur) { 47 | Intent it = new Intent(this, CardFlowLayoutActivity.class); 48 | it.putExtra(CardFlowLayoutActivity.ANIM_MODE, CardFlowLayoutActivity.MODE_BLUR); 49 | startActivity(it); 50 | } else if (v == btnLayoutMove) { 51 | Intent it = new Intent(this, CardFlowLayoutActivity.class); 52 | it.putExtra(CardFlowLayoutActivity.ANIM_MODE, CardFlowLayoutActivity.MODE_MOVE); 53 | startActivity(it); 54 | } else if (v == btnLayoutScale) { 55 | Intent it = new Intent(this, CardFlowLayoutActivity.class); 56 | it.putExtra(CardFlowLayoutActivity.ANIM_MODE, CardFlowLayoutActivity.MODE_SCALE); 57 | startActivity(it); 58 | } else { 59 | Intent it = new Intent(this, CardFlowLayoutActivity.class); 60 | it.putExtra(CardFlowLayoutActivity.ANIM_MODE, CardFlowLayoutActivity.MODE_CROSS_MOVE); 61 | startActivity(it); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/moe/codeest/ecardflowdemo/adapter/ImageAdapter.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo.adapter; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentPagerAdapter; 6 | 7 | import java.util.List; 8 | 9 | import moe.codeest.ecardflowdemo.fragment.ImageFragment; 10 | 11 | /** 12 | * Created by codeest on 2017/1/17. 13 | */ 14 | 15 | public class ImageAdapter extends FragmentPagerAdapter{ 16 | 17 | private List mList; 18 | 19 | public ImageAdapter(FragmentManager fm, List mList) { 20 | super(fm); 21 | this.mList = mList; 22 | } 23 | 24 | @Override 25 | public Fragment getItem(int position) { 26 | return mList.get(position); 27 | } 28 | 29 | @Override 30 | public int getCount() { 31 | return mList.size(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/moe/codeest/ecardflowdemo/adapter/ItemAdapter.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo.adapter; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import moe.codeest.ecardflowdemo.R; 10 | 11 | import static moe.codeest.ecardflowdemo.fragment.PagerFragment.dp2px; 12 | 13 | /** 14 | * Created by codeest on 2017/1/13. 15 | */ 16 | 17 | public class ItemAdapter extends RecyclerView.Adapter{ 18 | 19 | private static final int DEFAULT_COUNT = 1; 20 | 21 | private int mCurrentItemCount = DEFAULT_COUNT; 22 | 23 | @Override 24 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 25 | return new DefaultViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false)); 26 | } 27 | 28 | @Override 29 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 30 | 31 | } 32 | 33 | @Override 34 | public int getItemCount() { 35 | return mCurrentItemCount; 36 | } 37 | 38 | public void setItemCount(int mCurrentItemCount) { 39 | this.mCurrentItemCount = mCurrentItemCount; 40 | } 41 | 42 | public static class DefaultViewHolder extends RecyclerView.ViewHolder { 43 | 44 | public DefaultViewHolder(View itemView) { 45 | super(itemView); 46 | } 47 | } 48 | 49 | public static class DefaultItemDecoration extends RecyclerView.ItemDecoration{ 50 | 51 | @Override 52 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 53 | super.getItemOffsets(outRect, view, parent, state); 54 | int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); 55 | if (position > -1) { 56 | if (position == 0) { 57 | outRect.set(0, dp2px(view.getContext(), 18), 0, 0); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/moe/codeest/ecardflowdemo/adapter/PagerAdapter.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo.adapter; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentPagerAdapter; 6 | 7 | import java.util.List; 8 | 9 | import moe.codeest.ecardflowdemo.fragment.PagerFragment; 10 | 11 | /** 12 | * Created by codeest on 2017/1/8. 13 | */ 14 | 15 | public class PagerAdapter extends FragmentPagerAdapter{ 16 | 17 | private List mList; 18 | 19 | public PagerAdapter(FragmentManager fm, List mList) { 20 | super(fm); 21 | this.mList = mList; 22 | } 23 | 24 | @Override 25 | public Fragment getItem(int position) { 26 | return mList.get(position); 27 | } 28 | 29 | @Override 30 | public int getCount() { 31 | return mList.size(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/moe/codeest/ecardflowdemo/fragment/ImageFragment.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import moe.codeest.ecardflowdemo.R; 13 | 14 | /** 15 | * Created by codeest on 2017/1/17. 16 | */ 17 | 18 | public class ImageFragment extends Fragment{ 19 | 20 | private TextView tvDes; 21 | private ImageView ivImg; 22 | 23 | public static ImageFragment newInstance(String str, int res) { 24 | ImageFragment fragment = new ImageFragment(); 25 | Bundle args = new Bundle(); 26 | args.putString("str", str); 27 | args.putInt("res", res); 28 | fragment.setArguments(args); 29 | return fragment; 30 | } 31 | 32 | @Nullable 33 | @Override 34 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 35 | View mView = inflater.inflate(R.layout.image_pager, null); 36 | ivImg = (ImageView) mView.findViewById(R.id.image); 37 | tvDes = (TextView) mView.findViewById(R.id.title); 38 | return mView; 39 | } 40 | 41 | @Override 42 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 43 | super.onViewCreated(view, savedInstanceState); 44 | ivImg.setImageResource(getArguments().getInt("res")); 45 | tvDes.setText(getArguments().getString("str")); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/moe/codeest/ecardflowdemo/fragment/PagerFragment.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo.fragment; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.ImageView; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import jp.wasabeef.recyclerview.animators.FadeInUpAnimator; 18 | import moe.codeest.ecardflowdemo.adapter.ItemAdapter; 19 | import moe.codeest.ecardflowdemo.R; 20 | 21 | /** 22 | * Created by codeest on 2017/1/9. 23 | */ 24 | 25 | public class PagerFragment extends Fragment{ 26 | 27 | private ImageView ivBack; 28 | private RecyclerView rvContent; 29 | private TextView tvBtn; 30 | 31 | private ItemAdapter mAdapter; 32 | 33 | private boolean isReadyShrink; 34 | private int mScrollDirection = 0; 35 | 36 | public static PagerFragment newInstance() { 37 | return new PagerFragment(); 38 | } 39 | 40 | @Nullable 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 43 | View mView = inflater.inflate(R.layout.fragment_pager, null); 44 | ivBack = (ImageView) mView.findViewById(R.id.iv_back); 45 | rvContent = (RecyclerView) mView.findViewById(R.id.rv_content); 46 | tvBtn = (TextView) mView.findViewById(R.id.tv_btn); 47 | return mView; 48 | } 49 | 50 | @Override 51 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 52 | super.onViewCreated(view, savedInstanceState); 53 | ivBack.setVisibility(View.GONE); 54 | mAdapter = new ItemAdapter(); 55 | rvContent.setLayoutManager(new LinearLayoutManager(getContext())); 56 | final FadeInUpAnimator mAnim = new FadeInUpAnimator(); 57 | mAnim.setInterpolator(new FastOutSlowInInterpolator()); 58 | rvContent.setItemAnimator(mAnim); 59 | rvContent.getItemAnimator().setAddDuration(400); 60 | rvContent.addItemDecoration(new ItemAdapter.DefaultItemDecoration()); 61 | rvContent.setAdapter(mAdapter); 62 | rvContent.addOnScrollListener(new RecyclerView.OnScrollListener() { 63 | @Override 64 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 65 | if (dy < 0) { 66 | mScrollDirection = -1; 67 | } else if (dy > 0) { 68 | mScrollDirection = 1; 69 | } 70 | super.onScrolled(recyclerView, dx, dy); 71 | } 72 | 73 | @Override 74 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 75 | if (newState == RecyclerView.SCROLL_STATE_IDLE && recyclerView.getChildAt(0).getY() >= dp2px(getContext(), 18)) { 76 | isReadyShrink = true; 77 | } 78 | if (newState == RecyclerView.SCROLL_STATE_DRAGGING && recyclerView.getChildAt(0).getY() >= dp2px(getContext(), 18) && isReadyShrink && mScrollDirection < 0) { 79 | isReadyShrink = false; 80 | mScrollDirection = 0; 81 | ((OnPageListener) getActivity()).onShrink(); 82 | } 83 | super.onScrollStateChanged(recyclerView, newState); 84 | } 85 | }); 86 | tvBtn.setOnClickListener(new View.OnClickListener() { 87 | @Override 88 | public void onClick(View v) { 89 | Toast.makeText(getContext(), "Check-In",Toast.LENGTH_SHORT).show(); 90 | } 91 | }); 92 | ivBack.setOnClickListener(new View.OnClickListener() { 93 | @Override 94 | public void onClick(View v) { 95 | isReadyShrink = false; 96 | mScrollDirection = 0; 97 | ((OnPageListener) getActivity()).onShrink(); 98 | } 99 | }); 100 | } 101 | 102 | public void loadMoreData() { 103 | ivBack.setVisibility(View.VISIBLE); 104 | mAdapter.setItemCount(4); 105 | mAdapter.notifyItemRangeInserted(1, 3); 106 | pageExpandAnim(); 107 | } 108 | 109 | public void hideMoreData() { 110 | ivBack.setVisibility(View.GONE); 111 | mAdapter.setItemCount(1); 112 | mAdapter.notifyItemRangeRemoved(1, 3); 113 | pageShrinkAnim(); 114 | } 115 | 116 | public static int dp2px(Context context, float dpValue) { 117 | final float scale = context.getResources().getDisplayMetrics().density; 118 | return (int) (dpValue * scale + 0.5f); 119 | } 120 | 121 | private void pageExpandAnim() { 122 | rvContent.animate().scaleX(0.9f).setDuration(700).start(); 123 | } 124 | 125 | private void pageShrinkAnim() { 126 | rvContent.animate().scaleX(1).setDuration(700).start(); 127 | } 128 | 129 | public interface OnPageListener { 130 | void onShrink(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/moe/codeest/ecardflowdemo/support/ZoomOutPageTransformer.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo.support; 2 | 3 | import android.support.v4.view.ViewPager; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by codeest on 2017/1/17. 8 | */ 9 | 10 | public class ZoomOutPageTransformer implements ViewPager.PageTransformer { 11 | private static final float MIN_SCALE = 0.85f; 12 | private static final float MIN_ALPHA = 0.5f; 13 | 14 | public void transformPage(View view, float position) { 15 | int pageWidth = view.getWidth(); 16 | int pageHeight = view.getHeight(); 17 | if (position < -1) { // [-Infinity,-1) 18 | // This page is way off-screen to the left. 19 | view.setAlpha(MIN_ALPHA); 20 | view.setScaleX(MIN_SCALE); 21 | view.setScaleY(MIN_SCALE); 22 | } else if (position <= 1) { // [-1,1] 23 | // Modify the default slide transition to shrink the page as well 24 | float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)); 25 | float vertMargin = pageHeight * (1 - scaleFactor) / 2; 26 | float horzMargin = pageWidth * (1 - scaleFactor) / 2; 27 | if (position < 0) { 28 | view.setTranslationX(horzMargin - vertMargin / 2); 29 | view.setScaleX(1 + 0.15f * position); 30 | view.setScaleY(1 + 0.15f * position); 31 | } else { 32 | view.setTranslationX(-horzMargin + vertMargin / 2); 33 | 34 | view.setScaleX(1 - 0.15f * position); 35 | view.setScaleY(1 - 0.15f * position); 36 | } 37 | 38 | // Scale the page down (between MIN_SCALE and 1) 39 | 40 | // Fade the page relative to its size. 41 | view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA)); 42 | 43 | } else { // (1,+Infinity] 44 | // This page is way off-screen to the right. 45 | view.setScaleX(MIN_SCALE); 46 | view.setScaleY(MIN_SCALE); 47 | view.setAlpha(MIN_ALPHA); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_round_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_cardflow.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 19 | 20 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_cardflow_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 23 | 24 | 31 | 32 | 39 | 40 | 47 | 48 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_pager.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 22 | 31 | 42 | 53 | 61 | 62 | 63 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/image_pager.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 19 | 28 | 34 | 43 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 25 | 30 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-xhdpi/back.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-xhdpi/cover.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-xhdpi/face.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-xhdpi/img1.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/img2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-xhdpi/img2.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/img3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-xhdpi/img3.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ECardFlowDemo 3 | Coder\nACGer 4 | Mir Orbit US\n2014 / 07 / 23 5 | 中国の鵜飼い(鸬鹚捕鱼)です! 6 | 宇宙ステーション『ミール』(Мир)から見下ろす地球の様々な姿、が主題のオリジナルイラスト本になります。 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/test/java/moe/codeest/ecardflowdemo/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflowdemo; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.2' 9 | 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | jcenter() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /ecardflow/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ecardflow/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | // JitPack Maven 3 | apply plugin: 'com.github.dcendents.android-maven' 4 | // Group 5 | group='com.github.codeestX' 6 | 7 | android { 8 | compileSdkVersion 25 9 | buildToolsVersion "25.0.2" 10 | 11 | defaultConfig { 12 | minSdkVersion 21 13 | targetSdkVersion 25 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 18 | 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | compile fileTree(dir: 'libs', include: ['*.jar']) 30 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 31 | exclude group: 'com.android.support', module: 'support-annotations' 32 | }) 33 | compile 'com.android.support:appcompat-v7:25.1.0' 34 | testCompile 'junit:junit:4.12' 35 | } 36 | -------------------------------------------------------------------------------- /ecardflow/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/chenyulong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /ecardflow/src/androidTest/java/moe/codeest/ecardflow/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("moe.codeest.ecardflow.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ecardflow/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/ECardFlow.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.support.v4.app.Fragment; 9 | import android.support.v4.view.ViewPager; 10 | import android.util.AttributeSet; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.ViewConfiguration; 14 | import android.view.animation.Interpolator; 15 | import android.widget.Scroller; 16 | 17 | import java.lang.reflect.Field; 18 | 19 | import moe.codeest.ecardflow.support.CardFlowTransformer; 20 | import moe.codeest.ecardflow.util.DimenUtils; 21 | 22 | /** 23 | * Created by codeest on 2017/1/8. 24 | */ 25 | 26 | public class ECardFlow extends ViewPager { 27 | 28 | private CardFlowTransformer mTransformer; 29 | private ScrollerCustomDuration mScroller; 30 | private OnExpandStateListener onExpandStateListener; 31 | 32 | public static final int SLIDE_UP_TO_EXPAND = 10; 33 | public static final int CLICK_TO_EXPAND = 11; 34 | 35 | private static final int DEFAULT_BASE_SWITCH_TIME = 200; 36 | private static final int DEFAULT_EXPAND_TIME = 700; 37 | private static final int DEFAULT_EXPAND_MODE = SLIDE_UP_TO_EXPAND; 38 | private static final int DEFAULT_SWITCH_TIME = DEFAULT_BASE_SWITCH_TIME * 6; 39 | private static final int DEFAULT_PRELOAD_PAGE_NUM = 3; 40 | 41 | private float mLastX, mLastY, mInterLastX, mInterLastY; 42 | private float mPageScaleX, mPageScaleY, mScaleX, mScaleY, mScrollY; 43 | private float mRate; 44 | private boolean hasReset = true; 45 | private boolean isExpanding = false; 46 | private boolean isSwitching = false; 47 | 48 | //Custom Attrs 49 | private int mSlop; 50 | private int mExpandTime; 51 | private int mExpandMode; 52 | private int mSwitchTime; 53 | private int mPreloadPageNum; 54 | private int mMaxRotateY; 55 | 56 | public ECardFlow(Context context) { 57 | super(context); 58 | } 59 | 60 | public ECardFlow(Context context, AttributeSet attrs) { 61 | super(context, attrs); 62 | 63 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.attr); 64 | mSwitchTime = ta.getInt(R.styleable.attr_switchTime, DEFAULT_SWITCH_TIME); 65 | mExpandMode = ta.getInt(R.styleable.attr_expandMode, DEFAULT_EXPAND_MODE); 66 | mExpandTime = ta.getInteger(R.styleable.attr_expandTime, DEFAULT_EXPAND_TIME); 67 | mPreloadPageNum = ta.getInteger(R.styleable.attr_preloadPageNum, DEFAULT_PRELOAD_PAGE_NUM); 68 | mSlop = ta.getInteger(R.styleable.attr_touchSlop, 0); 69 | mMaxRotateY = ta.getInteger(R.styleable.attr_touchSlop, CardFlowTransformer.MAX_ROTATE_Y); 70 | ta.recycle(); 71 | 72 | mTransformer = new CardFlowTransformer(); 73 | mTransformer.setMaxRotateY(mMaxRotateY); 74 | setPageTransformer(true, mTransformer); 75 | initSwitchSpeed(mSwitchTime * 1.00f / DEFAULT_BASE_SWITCH_TIME); 76 | initExpandRate(); 77 | mSlop = ViewConfiguration.get(this.getContext()).getScaledTouchSlop(); 78 | addOnPageChangeListener(new OnDirectionListener()); 79 | setOffscreenPageLimit(mPreloadPageNum); 80 | } 81 | 82 | @Override 83 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 84 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 85 | if (widthSize >= DimenUtils.getScreenWidth(getContext().getApplicationContext())) { 86 | widthSize = DimenUtils.getScreenWidth(getContext().getApplicationContext()) * 4 / 5; 87 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY); 88 | } 89 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 90 | } 91 | 92 | @Override 93 | public boolean onTouchEvent(MotionEvent event) { 94 | if (isExpanding) { 95 | return false; 96 | } 97 | int action = event.getAction(); 98 | switch (action) { 99 | case MotionEvent.ACTION_MOVE: 100 | if(mLastX == 0) { 101 | mLastX = (int) event.getRawX(); 102 | } 103 | if(mLastY == 0) { 104 | mLastY = (int) event.getRawY(); 105 | } 106 | int mCurX = (int) event.getRawX(); 107 | int mCurY = (int) event.getRawY(); 108 | if (Math.abs(mCurX - mLastX) > mSlop && hasReset) { 109 | hasReset = false; 110 | if (mCurX > mLastX) { 111 | gotoLast(); 112 | } else { 113 | gotoNext(); 114 | } 115 | } else if(mExpandMode == SLIDE_UP_TO_EXPAND && mLastY - mCurY > mSlop && hasReset && !isSwitching) { 116 | hasReset = false; 117 | expand(); 118 | } 119 | break; 120 | case MotionEvent.ACTION_UP: 121 | int mUpX = (int) event.getRawX(); 122 | int mUpY = (int) event.getRawY(); 123 | if (mExpandMode == CLICK_TO_EXPAND && Math.abs(mUpX - mInterLastX) <= mSlop && Math.abs(mUpY - mInterLastY) <= mSlop && !isSwitching) { 124 | expand(); 125 | } 126 | mLastX = 0; 127 | mLastY = 0; 128 | hasReset = true; 129 | break; 130 | } 131 | return true; 132 | } 133 | 134 | @Override 135 | public boolean onInterceptTouchEvent(MotionEvent event) { 136 | int action = event.getAction(); 137 | switch (action) { 138 | case MotionEvent.ACTION_DOWN: 139 | if (isSwitching) { 140 | return false; 141 | } 142 | mInterLastX = (int) event.getRawX(); 143 | mInterLastY = (int) event.getRawY(); 144 | break; 145 | case MotionEvent.ACTION_MOVE: 146 | return !isExpanding; 147 | case MotionEvent.ACTION_UP: 148 | mLastX = 0; 149 | mLastY = 0; 150 | mInterLastX = 0; 151 | mInterLastY = 0; 152 | hasReset = true; 153 | } 154 | return super.onInterceptTouchEvent(event); 155 | } 156 | 157 | public void gotoNext() { 158 | setCurrentItem(getCurrentItem() + 1); 159 | } 160 | 161 | public void gotoLast() { 162 | setCurrentItem(getCurrentItem() - 1); 163 | } 164 | 165 | public void setExpandTime(int time) { 166 | mExpandTime = time; 167 | } 168 | 169 | public void setTouchSlop(int slop) { 170 | mSlop = slop; 171 | } 172 | 173 | public boolean isExpanding() { 174 | return isExpanding; 175 | } 176 | 177 | public void expand() { 178 | isExpanding = true; 179 | View pageView = getPageView(); 180 | if (pageView == null) 181 | return; 182 | cacheData(pageView); 183 | int location[] = new int[2]; 184 | getLocationInWindow(location); 185 | pageView.animate().scaleX(1).setDuration(mExpandTime).start(); 186 | pageView.animate().scaleY(1).setDuration(mExpandTime).start(); 187 | mScrollY = (mRate - 1f) * getHeight() / 2 - location[1]; 188 | animate().scaleX(mRate).setDuration(mExpandTime).start(); 189 | animate().scaleY(mRate).setDuration(mExpandTime).start(); 190 | ObjectAnimator anim = ObjectAnimator.ofFloat(this, "translationY", 0, mScrollY).setDuration(mExpandTime); 191 | anim.addListener(new AnimatorListenerAdapter() { 192 | @Override 193 | public void onAnimationEnd(Animator animation) { 194 | ECardFlow.this.setPadding( 195 | ECardFlow.this.getPaddingLeft(), 196 | ECardFlow.this.getPaddingTop(), 197 | ECardFlow.this.getPaddingRight(), 198 | ECardFlow.this.getPaddingBottom() + getExtraPaddingBottom()); 199 | super.onAnimationEnd(animation); 200 | } 201 | }); 202 | anim.start(); 203 | if (onExpandStateListener != null) { 204 | onExpandStateListener.onExpand(pageView, getCurrentItem()); 205 | } 206 | } 207 | 208 | public void shrink() { 209 | isExpanding = false; 210 | View pageView = getPageView(); 211 | if (pageView == null) 212 | return; 213 | pageView.animate().scaleX(mPageScaleX).setDuration(mExpandTime).start(); 214 | pageView.animate().scaleY(mPageScaleY).setDuration(mExpandTime).start(); 215 | animate().scaleX(mScaleX).setDuration(mExpandTime).start(); 216 | animate().scaleY(mScaleY).setDuration(mExpandTime).start(); 217 | ObjectAnimator anim = ObjectAnimator.ofFloat(this, "translationY", mScrollY, 0).setDuration(mExpandTime); 218 | anim.addListener(new AnimatorListenerAdapter() { 219 | @Override 220 | public void onAnimationStart(Animator animation) { 221 | ECardFlow.this.setPadding( 222 | ECardFlow.this.getPaddingLeft(), 223 | ECardFlow.this.getPaddingTop(), 224 | ECardFlow.this.getPaddingRight(), 225 | ECardFlow.this.getPaddingBottom() - getExtraPaddingBottom()); 226 | super.onAnimationEnd(animation); 227 | } 228 | }); 229 | anim.start(); 230 | if (onExpandStateListener != null) { 231 | onExpandStateListener.onShrink(pageView, getCurrentItem()); 232 | } 233 | } 234 | 235 | private View getPageView() { 236 | try { 237 | Fragment fragment = (Fragment) getAdapter().instantiateItem(this, getCurrentItem()); 238 | return fragment.getView(); 239 | } catch (ClassCastException e) { 240 | throw new ClassCastException("Only support fragment page"); 241 | } 242 | } 243 | 244 | private int getExtraPaddingBottom() { 245 | if (getHeight() * mRate > DimenUtils.getScreenHeight(getContext().getApplicationContext())) { 246 | return (int) ((getHeight() * mRate - DimenUtils.getScreenHeight(getContext().getApplicationContext())) / mRate); 247 | } else { 248 | return 0; 249 | } 250 | } 251 | 252 | private void cacheData(View view) { 253 | mPageScaleX = view.getScaleX(); 254 | mPageScaleY = view.getScaleY(); 255 | mScaleX = getScaleX(); 256 | mScaleY = getScaleY(); 257 | } 258 | 259 | private class OnDirectionListener implements OnPageChangeListener { 260 | 261 | private float mLastOffset; 262 | 263 | @Override 264 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 265 | if (mTransformer != null && positionOffset != 0 && positionOffset != mLastOffset) { 266 | if (positionOffset < mLastOffset) { 267 | mTransformer.setDirection(CardFlowTransformer.D_LEFT); 268 | } else { 269 | mTransformer.setDirection(CardFlowTransformer.D_RIGHT); 270 | } 271 | mLastOffset = positionOffset; 272 | } 273 | } 274 | 275 | @Override 276 | public void onPageSelected(int position) { 277 | 278 | } 279 | 280 | @Override 281 | public void onPageScrollStateChanged(int state) { 282 | isSwitching = state != ViewPager.SCROLL_STATE_IDLE; 283 | } 284 | } 285 | 286 | void initExpandRate() { 287 | post(new Runnable() { 288 | @Override 289 | public void run() { 290 | mRate = DimenUtils.getScreenWidth(getContext().getApplicationContext()) * 1.00f / getWidth(); 291 | } 292 | }); 293 | } 294 | 295 | private void initSwitchSpeed(float scrollFactor) { 296 | try { 297 | Class viewpager = ViewPager.class; 298 | Field scroller = viewpager.getDeclaredField("mScroller"); 299 | scroller.setAccessible(true); 300 | Field interpolator = viewpager.getDeclaredField("sInterpolator"); 301 | interpolator.setAccessible(true); 302 | 303 | mScroller = new ScrollerCustomDuration(getContext(), 304 | (Interpolator) interpolator.get(null)); 305 | mScroller.setScrollFactor(scrollFactor); 306 | scroller.set(this, mScroller); 307 | } catch (Exception e) { 308 | 309 | } 310 | } 311 | 312 | public static class ScrollerCustomDuration extends Scroller { 313 | 314 | private double mScrollFactor = DEFAULT_SWITCH_TIME / DEFAULT_BASE_SWITCH_TIME; 315 | 316 | public ScrollerCustomDuration(Context context, Interpolator interpolator) { 317 | super(context, interpolator); 318 | } 319 | 320 | @Override 321 | public void startScroll(int startX, int startY, int dx, int dy, int duration) { 322 | super.startScroll(startX, startY, dx, dy, (int) (duration * mScrollFactor)); 323 | } 324 | 325 | public void setScrollFactor(double mScrollFactor) { 326 | this.mScrollFactor = mScrollFactor; 327 | } 328 | } 329 | 330 | public interface OnExpandStateListener { 331 | void onExpand(View page, int position); 332 | 333 | void onShrink(View page, int position); 334 | } 335 | 336 | public void setOnExpandStateListener (OnExpandStateListener onExpandStateListener) { 337 | this.onExpandStateListener = onExpandStateListener; 338 | } 339 | } -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/ECardFlowLayout.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Bitmap; 6 | import android.graphics.drawable.BitmapDrawable; 7 | import android.graphics.drawable.Drawable; 8 | import android.graphics.drawable.TransitionDrawable; 9 | import android.os.Build; 10 | import android.os.Handler; 11 | import android.os.Message; 12 | import android.renderscript.RSRuntimeException; 13 | import android.support.annotation.Nullable; 14 | import android.support.v4.util.LruCache; 15 | import android.support.v4.view.ViewPager; 16 | import android.util.AttributeSet; 17 | import android.util.Log; 18 | import android.view.ViewGroup; 19 | import android.widget.FrameLayout; 20 | import android.widget.ImageView; 21 | 22 | import java.lang.ref.WeakReference; 23 | import java.util.concurrent.ExecutorService; 24 | import java.util.concurrent.Executors; 25 | 26 | import moe.codeest.ecardflow.mode.AnimMode; 27 | import moe.codeest.ecardflow.mode.BlurAnimMode; 28 | import moe.codeest.ecardflow.provider.ImageProvider; 29 | import moe.codeest.ecardflow.support.RecyclingBitmapDrawable; 30 | import moe.codeest.ecardflow.util.FastBlur; 31 | import moe.codeest.ecardflow.util.RSBlur; 32 | 33 | /** 34 | * Created by codeest on 2017/1/16. 35 | */ 36 | 37 | public class ECardFlowLayout extends FrameLayout{ 38 | 39 | private static final int SWITCH_ANIM_TIME = 300; //300 ms 40 | private static final int MSG_JUDGE_RESET = 0x1; 41 | 42 | private Context mContext; 43 | private ExecutorService mThreadPool; 44 | private LruCache mLruCache; 45 | private MyHandler mHandler; 46 | private NotifyRunnable mNotifyRunnable; 47 | 48 | private ImageView mBgImage; 49 | private ImageView mBlurImage; 50 | private ViewPager mViewPager; 51 | 52 | private ImageProvider mProvider; 53 | private AnimMode mAnimMode; 54 | 55 | private RecyclingBitmapDrawable curBp, lastBp, nextBp; 56 | private Bitmap curBlurBp, lastBlurBp, nextBlurBp; 57 | 58 | private int mCurDirection = AnimMode.D_RIGHT; 59 | private int mSwitchAnimTime = SWITCH_ANIM_TIME; 60 | private int mMinCacheSize; 61 | private float mLastOffset; 62 | private int mRadius; 63 | private int mCurPosition; 64 | private int mLastPosition; 65 | private boolean isSwitching; 66 | 67 | public ECardFlowLayout(Context context) { 68 | super(context); 69 | } 70 | 71 | public ECardFlowLayout(Context context, AttributeSet attrs) { 72 | super(context, attrs); 73 | mContext = context; 74 | 75 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.attr_layout); 76 | mSwitchAnimTime = ta.getInt(R.styleable.attr_layout_switchAnimTime, SWITCH_ANIM_TIME); 77 | ta.recycle(); 78 | 79 | init(); 80 | } 81 | 82 | @Override 83 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 84 | super.onSizeChanged(w, h, oldw, oldh); 85 | initViewPager(); 86 | } 87 | 88 | private void init() { 89 | mThreadPool = Executors.newCachedThreadPool(); 90 | mNotifyRunnable = new NotifyRunnable(); 91 | mHandler = new MyHandler(this); 92 | initCache(); 93 | mBlurImage = new ImageView(mContext); 94 | initImageView(mBlurImage); 95 | mBgImage = new ImageView(mContext); 96 | initImageView(mBgImage); 97 | } 98 | 99 | private void initImageView(ImageView image) { 100 | image.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 101 | image.setScaleType(ImageView.ScaleType.CENTER_CROP); 102 | addView(image); 103 | } 104 | 105 | private void initViewPager() { 106 | for (int i= 0; i< getChildCount(); i++) { 107 | if (getChildAt(i) instanceof ViewPager) { 108 | mViewPager = (ViewPager) getChildAt(i); 109 | } 110 | } 111 | if (mViewPager == null) { 112 | throw new IllegalStateException("Can't find ViewPager in ECardFlowLayout"); 113 | } 114 | mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 115 | @Override 116 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 117 | if (positionOffset != 0 && positionOffset != mLastOffset) { 118 | if (positionOffset < mLastOffset) { 119 | mCurDirection = AnimMode.D_LEFT; 120 | } else { 121 | mCurDirection = AnimMode.D_RIGHT; 122 | } 123 | mLastOffset = positionOffset; 124 | } 125 | int lastPosition = Math.round(position + positionOffset); 126 | if (mLastPosition != lastPosition) { 127 | if (mCurDirection == AnimMode.D_LEFT) { 128 | switchBgToLast(lastPosition); 129 | } else { 130 | switchBgToNext(lastPosition); 131 | } 132 | } 133 | mLastPosition = lastPosition; 134 | if (mAnimMode != null) { 135 | mAnimMode.transformPage(mBgImage, position + positionOffset, mCurDirection); 136 | } 137 | } 138 | 139 | @Override 140 | public void onPageSelected(int position) { 141 | 142 | } 143 | 144 | @Override 145 | public void onPageScrollStateChanged(int state) { 146 | 147 | } 148 | }); 149 | } 150 | 151 | private void initCache() { 152 | int maxMemory = (int) Runtime.getRuntime().maxMemory() / 1024; 153 | int cacheSize = maxMemory / 5; 154 | mMinCacheSize = maxMemory / 8; 155 | mLruCache = new LruCache(cacheSize) { 156 | @Override 157 | protected int sizeOf(String key, RecyclingBitmapDrawable value) { 158 | return value.getBitmap().getByteCount() / 1024; 159 | } 160 | 161 | @Override 162 | protected void entryRemoved(boolean evicted, String key, RecyclingBitmapDrawable oldValue, RecyclingBitmapDrawable newValue) { 163 | super.entryRemoved(evicted, key, oldValue, newValue); 164 | if (evicted && oldValue != null) { 165 | oldValue.setIsCached(false); 166 | } 167 | } 168 | 169 | @Override 170 | protected RecyclingBitmapDrawable create(String key) { 171 | RecyclingBitmapDrawable bitmap = new RecyclingBitmapDrawable(getResources(), mProvider.onProvider(Integer.valueOf(key))); 172 | if (bitmap.getBitmap() == null) 173 | return null; 174 | bitmap.setIsCached(true); 175 | return bitmap; 176 | } 177 | }; 178 | } 179 | 180 | private void updateNextRes(final int position) { 181 | mCurPosition = position; 182 | detachBitmap(lastBp); 183 | lastBp = curBp; 184 | curBp = nextBp; 185 | if (mBlurImage != null) { 186 | recycleBitmap(lastBlurBp); 187 | lastBlurBp = curBlurBp; 188 | curBlurBp = nextBlurBp; 189 | } 190 | if (mProvider != null) { 191 | mThreadPool.execute(new Runnable() { 192 | @Override 193 | public void run() { 194 | nextBp = loadBitmap(position + 1); 195 | if (mBlurImage != null) { 196 | nextBlurBp = blurBitmap(position + 1); 197 | } 198 | sendMsg(); 199 | } 200 | }); 201 | } else { 202 | throw new RuntimeException("setImageProvider is necessary"); 203 | } 204 | } 205 | 206 | private void updateLastRes(final int position) { 207 | mCurPosition = position; 208 | detachBitmap(nextBp); 209 | nextBp = curBp; 210 | curBp = lastBp; 211 | if (mBlurImage != null) { 212 | recycleBitmap(nextBlurBp); 213 | nextBlurBp = curBlurBp; 214 | curBlurBp = lastBlurBp; 215 | } 216 | if (mProvider != null) { 217 | mThreadPool.execute(new Runnable() { 218 | @Override 219 | public void run() { 220 | lastBp = loadBitmap(position - 1); 221 | if (mBlurImage != null) { 222 | lastBlurBp = blurBitmap(position - 1); 223 | } 224 | sendMsg(); 225 | } 226 | }); 227 | } else { 228 | throw new IllegalArgumentException("setImageProvider is necessary"); 229 | } 230 | } 231 | 232 | private void startTrans(int targetPosition, ImageView targetImage, RecyclingBitmapDrawable startBp, RecyclingBitmapDrawable endBp) { 233 | if (endBp == null) 234 | endBp = loadBitmap(targetPosition); 235 | TransitionDrawable td = new TransitionDrawable(new Drawable[] {startBp, endBp}); 236 | targetImage.setImageDrawable(td); 237 | td.setCrossFadeEnabled(true); 238 | td.startTransition(mSwitchAnimTime); 239 | } 240 | 241 | private void switchBgToNext(final int targetPosition) { 242 | if (isSwitching) { 243 | return; 244 | } 245 | isSwitching = true; 246 | startTrans(targetPosition + 1, mBgImage, curBp, nextBp); 247 | if (mBlurImage != null) { 248 | startTrans(targetPosition + 1, mBlurImage, new RecyclingBitmapDrawable(getResources(), curBlurBp), new RecyclingBitmapDrawable(getResources(), nextBlurBp)); 249 | } 250 | mNotifyRunnable.setTarget(targetPosition, true); 251 | mBgImage.postDelayed(mNotifyRunnable, mSwitchAnimTime); 252 | } 253 | 254 | private void switchBgToLast(final int targetPosition) { 255 | if (isSwitching) { 256 | return; 257 | } 258 | isSwitching = true; 259 | startTrans(targetPosition - 1, mBgImage, curBp, lastBp); 260 | if (mBlurImage != null) { 261 | startTrans(targetPosition - 1, mBlurImage, new RecyclingBitmapDrawable(getResources(), curBlurBp), new RecyclingBitmapDrawable(getResources(), lastBlurBp)); 262 | } 263 | mNotifyRunnable.setTarget(targetPosition, false); 264 | mBgImage.postDelayed(mNotifyRunnable, mSwitchAnimTime); 265 | } 266 | 267 | private void jumpBgToTarget(final int targetPosition) { 268 | mCurPosition = targetPosition; 269 | if (isSwitching) { 270 | return; 271 | } 272 | isSwitching = true; 273 | final RecyclingBitmapDrawable newBitmap = loadBitmap(targetPosition); 274 | TransitionDrawable td = new TransitionDrawable(new Drawable[] {curBp, newBitmap}); 275 | mBgImage.setImageDrawable(td); 276 | td.setCrossFadeEnabled(true); 277 | td.startTransition(mSwitchAnimTime); 278 | if (mBlurImage != null) { 279 | TransitionDrawable tdb = new TransitionDrawable(new Drawable[] {new BitmapDrawable(mContext.getResources(), curBlurBp), 280 | new BitmapDrawable(mContext.getResources(), blurBitmap(targetPosition))}); 281 | mBlurImage.setImageDrawable(tdb); 282 | tdb.setCrossFadeEnabled(true); 283 | tdb.startTransition(mSwitchAnimTime); 284 | } 285 | mBgImage.postDelayed(new Runnable() { 286 | @Override 287 | public void run() { 288 | mThreadPool.execute(new Runnable() { 289 | @Override 290 | public void run() { 291 | detachBitmap(nextBp); 292 | detachBitmap(lastBp); 293 | detachBitmap(curBp); 294 | curBp = newBitmap; 295 | nextBp = loadBitmap(targetPosition + 1); 296 | lastBp = loadBitmap(targetPosition - 1); 297 | if (mBlurImage != null) { 298 | recycleBitmap(nextBlurBp); 299 | recycleBitmap(lastBlurBp); 300 | recycleBitmap(curBlurBp); 301 | curBlurBp = blurBitmap(targetPosition); 302 | nextBlurBp = blurBitmap(targetPosition + 1); 303 | lastBlurBp = blurBitmap(targetPosition - 1); 304 | } 305 | sendMsg(); 306 | } 307 | }); 308 | } 309 | }, mSwitchAnimTime); 310 | } 311 | 312 | private void sendMsg() { 313 | Message msg = new Message(); 314 | msg.what = MSG_JUDGE_RESET; 315 | if (mHandler != null) { 316 | mHandler.sendMessage(msg); 317 | } 318 | } 319 | 320 | private void judgeReset() { 321 | isSwitching = false; 322 | if (Math.abs(mCurPosition - mLastPosition) <= 1) { 323 | if (mCurPosition > mLastPosition) { 324 | switchBgToLast(mLastPosition); 325 | } else if (mCurPosition < mLastPosition) { 326 | switchBgToNext(mLastPosition); 327 | } 328 | } else { 329 | jumpBgToTarget(mLastPosition); 330 | } 331 | } 332 | 333 | @Nullable 334 | private Bitmap blurBitmap(int targetPosition) { 335 | RecyclingBitmapDrawable bitmapDrawable = mLruCache.get(String.valueOf(targetPosition)); 336 | if (bitmapDrawable == null || bitmapDrawable.getBitmap() == null) { 337 | return null; 338 | } 339 | Bitmap blurBitmap = bitmapDrawable.getBitmap().copy(Bitmap.Config.ARGB_8888, true); 340 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 341 | try { 342 | blurBitmap = RSBlur.blur(mContext, blurBitmap, mRadius); 343 | } catch (RSRuntimeException e) { 344 | blurBitmap = FastBlur.blur(blurBitmap, mRadius, true); 345 | } 346 | } else { 347 | blurBitmap = FastBlur.blur(blurBitmap, mRadius, true); 348 | } 349 | return blurBitmap; 350 | } 351 | 352 | private RecyclingBitmapDrawable loadBitmap(int targetPosition) { 353 | RecyclingBitmapDrawable bitmap = mLruCache.get(String.valueOf(targetPosition)); 354 | if (bitmap != null) { 355 | bitmap.setIsDisplayed(true); 356 | } 357 | return bitmap; 358 | } 359 | 360 | private void recycleBitmap(Bitmap bitmap) { 361 | if (bitmap != null) 362 | bitmap.recycle(); 363 | } 364 | 365 | private void detachBitmap(RecyclingBitmapDrawable bitmap) { 366 | if (bitmap != null) { 367 | bitmap.setIsDisplayed(false); 368 | } 369 | } 370 | 371 | public void setImageProvider(ImageProvider provider) { 372 | mProvider = provider; 373 | curBp = loadBitmap(0); 374 | nextBp = loadBitmap(1); 375 | if (mAnimMode == null) { 376 | throw new IllegalStateException("You should setAnimMode before setImageProvider"); 377 | } 378 | if (mBlurImage != null) { 379 | curBlurBp = blurBitmap(0); 380 | nextBlurBp = blurBitmap(1); 381 | mBlurImage.setImageBitmap(blurBitmap(0)); 382 | } 383 | mBgImage.setImageBitmap(curBp.getBitmap()); 384 | } 385 | 386 | public void setAnimMode(AnimMode animMode) { 387 | mAnimMode = animMode; 388 | if (!(mAnimMode instanceof BlurAnimMode)) { 389 | removeView(mBlurImage); 390 | mBlurImage = null; 391 | } else { 392 | mRadius = ((BlurAnimMode) mAnimMode).getBlurRadius(); 393 | } 394 | } 395 | 396 | public void setSwitchAnimTime(int switchAnimTime) { 397 | mSwitchAnimTime = switchAnimTime; 398 | } 399 | 400 | public void setCacheSize(int megabytes) { 401 | if (megabytes * 1024 >= mMinCacheSize) { 402 | mLruCache.resize(megabytes * 1024); 403 | } else { 404 | Log.w(getClass().getName(), "Size is too small to resize"); 405 | } 406 | } 407 | 408 | public void onDestroy() { 409 | mHandler.removeCallbacksAndMessages(null); 410 | if (!mThreadPool.isShutdown()) { 411 | mThreadPool.shutdown(); 412 | } 413 | mHandler = null; 414 | detachBitmap(curBp); 415 | detachBitmap(lastBp); 416 | detachBitmap(nextBp); 417 | mLruCache.evictAll(); 418 | if (mBlurImage != null) { 419 | recycleBitmap(curBlurBp); 420 | recycleBitmap(lastBlurBp); 421 | recycleBitmap(nextBlurBp); 422 | } 423 | } 424 | 425 | private class NotifyRunnable implements Runnable { 426 | 427 | private int targetPosition; 428 | private boolean isNext; 429 | 430 | @Override 431 | public void run() { 432 | if (isNext) { 433 | updateNextRes(targetPosition); 434 | } else { 435 | updateLastRes(targetPosition); 436 | } 437 | } 438 | 439 | void setTarget(int targetPosition, boolean isNext) { 440 | this.targetPosition = targetPosition; 441 | this.isNext = isNext; 442 | } 443 | } 444 | 445 | private static class MyHandler extends Handler { 446 | 447 | WeakReference mLayout; 448 | 449 | MyHandler(ECardFlowLayout layout) { 450 | mLayout = new WeakReference<>(layout); 451 | } 452 | 453 | @Override 454 | public void handleMessage(Message msg) { 455 | ECardFlowLayout layout = mLayout.get(); 456 | switch (msg.what) { 457 | case MSG_JUDGE_RESET: 458 | layout.judgeReset(); 459 | break; 460 | } 461 | } 462 | } 463 | } -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/mode/AnimMode.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.mode; 2 | 3 | import android.widget.ImageView; 4 | 5 | /** 6 | * Created by codeest on 2017/1/20. 7 | */ 8 | 9 | public interface AnimMode { 10 | 11 | int D_LEFT = -1; 12 | int D_RIGHT = 1; 13 | 14 | void transformPage(ImageView ivBg, float position, int direction); 15 | } 16 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/mode/BlurAnimMode.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.mode; 2 | 3 | import android.widget.ImageView; 4 | 5 | /** 6 | * Created by codeest on 2017/1/20. 7 | */ 8 | 9 | public class BlurAnimMode implements AnimMode { 10 | 11 | private static final int DEFAULT_BLUR_RADIUS = 25; 12 | private int mRadius; 13 | 14 | public BlurAnimMode() { 15 | this.mRadius = DEFAULT_BLUR_RADIUS; 16 | } 17 | 18 | public BlurAnimMode(int radius) { 19 | this.mRadius = radius; 20 | } 21 | 22 | @Override 23 | public void transformPage(ImageView ivBg, float position, int direction) { 24 | float mFraction = (float) Math.cos(2 * Math.PI * position); 25 | if (mFraction < 0) 26 | mFraction = 0; 27 | ivBg.setAlpha(mFraction); 28 | } 29 | 30 | public int getBlurRadius() { 31 | return mRadius; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/mode/CrossMoveAnimMode.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.mode; 2 | 3 | import android.widget.ImageView; 4 | 5 | /** 6 | * Created by codeest on 2017/2/25. 7 | */ 8 | 9 | public class CrossMoveAnimMode implements AnimMode { 10 | 11 | private static final float DEFAULT_MOVE_SCALE = 1.3f; 12 | private float mScale; 13 | 14 | public CrossMoveAnimMode() { 15 | this.mScale = DEFAULT_MOVE_SCALE; 16 | } 17 | 18 | public CrossMoveAnimMode(float mScale) { 19 | this.mScale = mScale; 20 | } 21 | 22 | @Override 23 | public void transformPage(ImageView ivBg, float position, int direction) { 24 | ivBg.setScaleX(mScale); 25 | ivBg.setScaleY(mScale); 26 | float totalMoveWidth = ivBg.getWidth() * ((mScale - 1) / 2); 27 | int lastPosition = Math.round(position); 28 | float mFraction; 29 | if (lastPosition % 2 == 0) { 30 | mFraction = -1 * (float) Math.sin(Math.PI * position); 31 | } else { 32 | mFraction = (float) Math.sin(Math.PI * position); 33 | } 34 | ivBg.setTranslationY(totalMoveWidth * mFraction); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/mode/DefaultAnimMode.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.mode; 2 | 3 | import android.widget.ImageView; 4 | 5 | /** 6 | * Created by codeest on 2017/1/21. 7 | */ 8 | 9 | public class DefaultAnimMode implements AnimMode{ 10 | 11 | @Override 12 | public void transformPage(ImageView ivBg, float position, int direction) { 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/mode/MoveAnimMode.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.mode; 2 | 3 | import android.widget.ImageView; 4 | 5 | /** 6 | * Created by codeest on 2017/1/20. 7 | */ 8 | 9 | public class MoveAnimMode implements AnimMode { 10 | 11 | private static final float DEFAULT_MOVE_SCALE = 1.3f; 12 | private float mScale; 13 | 14 | public MoveAnimMode() { 15 | this.mScale = DEFAULT_MOVE_SCALE; 16 | } 17 | 18 | public MoveAnimMode(float mScale) { 19 | this.mScale = mScale; 20 | } 21 | 22 | @Override 23 | public void transformPage(ImageView ivBg, float position, int direction) { 24 | ivBg.setScaleX(mScale); 25 | ivBg.setScaleY(mScale); 26 | float totalMoveWidth = ivBg.getWidth() * ((mScale - 1) / 2); 27 | int lastPosition = Math.round(position); 28 | float mFraction; 29 | if (lastPosition % 2 == 0) { 30 | mFraction = -1 * (float) Math.sin(Math.PI * position); 31 | } else { 32 | mFraction = (float) Math.sin(Math.PI * position); 33 | } 34 | ivBg.setTranslationX(totalMoveWidth * mFraction); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/mode/ScaleAnimMode.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.mode; 2 | 3 | import android.widget.ImageView; 4 | 5 | /** 6 | * Created by codeest on 2017/2/25. 7 | */ 8 | 9 | public class ScaleAnimMode implements AnimMode { 10 | 11 | private static final int DEFAULT_SCALE_RATE = 1; 12 | private int mScaleRate; 13 | 14 | public ScaleAnimMode() { 15 | this.mScaleRate = DEFAULT_SCALE_RATE; 16 | } 17 | 18 | public ScaleAnimMode(int scaleRate) { 19 | this.mScaleRate = scaleRate; 20 | } 21 | 22 | @Override 23 | public void transformPage(ImageView ivBg, float position, int direction) { 24 | float mFraction = mScaleRate * (float) Math.abs(Math.sin(Math.PI * position)); 25 | ivBg.setScaleX(1 + mFraction); 26 | ivBg.setScaleY(1 + mFraction); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/provider/DrawableImageProvider.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.provider; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.support.annotation.Nullable; 7 | 8 | /** 9 | * Created by codeest on 2017/1/19. 10 | */ 11 | 12 | public class DrawableImageProvider implements ImageProvider{ 13 | 14 | private int[] mRes; 15 | private int width, height; 16 | private Context mContext; 17 | 18 | public DrawableImageProvider(Context mContext, int[] res, int width, int height) { 19 | this.mRes = res; 20 | this.mContext = mContext; 21 | this.width = width; 22 | this.height = height; 23 | } 24 | 25 | @Nullable 26 | @Override 27 | public Bitmap onProvider(int position) { 28 | if (position >= 0 && position < mRes.length) { 29 | BitmapFactory.Options options = new BitmapFactory.Options(); 30 | options.inJustDecodeBounds = true; 31 | BitmapFactory.decodeResource(mContext.getResources(), mRes[position], options); 32 | int inSampleSize = Math.min(options.outWidth / width, options.outHeight / height); 33 | int dstSample = 1; 34 | if (inSampleSize > dstSample) { 35 | dstSample = inSampleSize; 36 | } 37 | options.inJustDecodeBounds = false; 38 | options.inSampleSize = dstSample; 39 | return BitmapFactory.decodeResource(mContext.getResources(), mRes[position], options); 40 | } else { 41 | return null; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/provider/FileImageProvider.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.provider; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.support.annotation.Nullable; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * Created by codeest on 2017/1/21. 11 | */ 12 | 13 | public class FileImageProvider implements ImageProvider{ 14 | 15 | private File[] mFile; 16 | private int width, height; 17 | 18 | public FileImageProvider(File[] files, int width, int height) { 19 | this.mFile = files; 20 | this.width = width; 21 | this.height = height; 22 | } 23 | 24 | @Nullable 25 | @Override 26 | public Bitmap onProvider(int position) { 27 | if (position >= 0 && position < mFile.length && mFile[position].exists()) { 28 | BitmapFactory.Options options = new BitmapFactory.Options(); 29 | options.inJustDecodeBounds = true; 30 | BitmapFactory.decodeFile(mFile[position].getPath(), options); 31 | int inSampleSize = Math.min(options.outWidth / width, options.outHeight / height); 32 | int dstSample = 1; 33 | if (inSampleSize > dstSample) { 34 | dstSample = inSampleSize; 35 | } 36 | options.inJustDecodeBounds = false; 37 | options.inSampleSize = dstSample; 38 | return BitmapFactory.decodeFile(mFile[position].getPath(), options); 39 | } else { 40 | return null; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/provider/ImageProvider.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.provider; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | /** 6 | * Created by codeest on 2017/1/19. 7 | */ 8 | 9 | public interface ImageProvider { 10 | 11 | Bitmap onProvider(int position); 12 | } 13 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/provider/PathImageProvider.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.provider; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.support.annotation.Nullable; 6 | 7 | /** 8 | * Created by codeest on 2017/1/21. 9 | */ 10 | 11 | public class PathImageProvider implements ImageProvider{ 12 | 13 | private String[] mPath; 14 | private int width, height; 15 | 16 | public PathImageProvider(String[] path, int width, int height) { 17 | this.mPath = path; 18 | this.width = width; 19 | this.height = height; 20 | } 21 | 22 | @Nullable 23 | @Override 24 | public Bitmap onProvider(int position) { 25 | if (position >= 0 && position < mPath.length) { 26 | BitmapFactory.Options options = new BitmapFactory.Options(); 27 | options.inJustDecodeBounds = true; 28 | BitmapFactory.decodeFile(mPath[position], options); 29 | int inSampleSize = Math.min(options.outWidth / width, options.outHeight / height); 30 | int dstSample = 1; 31 | if (inSampleSize > dstSample) { 32 | dstSample = inSampleSize; 33 | } 34 | options.inJustDecodeBounds = false; 35 | options.inSampleSize = dstSample; 36 | return BitmapFactory.decodeFile(mPath[position], options); 37 | } else { 38 | return null; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/support/CardFlowTransformer.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.support; 2 | 3 | import android.support.v4.view.ViewPager; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by codeest on 2017/1/8. 8 | */ 9 | 10 | public class CardFlowTransformer implements ViewPager.PageTransformer { 11 | 12 | public static final int MAX_ROTATE_Y = 5; 13 | private static final float PAGE_SCALE = 0.9f; 14 | public static final int D_LEFT = -1; 15 | public static final int D_RIGHT = 1; 16 | 17 | private int mDirection = D_LEFT; 18 | private float mMaxRotateY = MAX_ROTATE_Y; 19 | 20 | @Override 21 | public void transformPage(View page, float position) { 22 | final float height = page.getHeight(); 23 | final float width = page.getWidth(); 24 | page.setPivotY(0.5f * height); 25 | page.setPivotX(0.5f * width); 26 | if (position >= -2 && position <= 2) { 27 | float mFraction = mDirection * (float) Math.abs(Math.sin(Math.PI * position)); 28 | page.setRotationY(mFraction * mMaxRotateY); 29 | page.setScaleX(PAGE_SCALE); 30 | page.setScaleY(PAGE_SCALE); 31 | } 32 | } 33 | 34 | public void setDirection(int mDirection) { 35 | this.mDirection = mDirection; 36 | } 37 | 38 | public void setMaxRotateY(int rotate) { 39 | mMaxRotateY = rotate; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/support/RecyclingBitmapDrawable.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.support; 2 | 3 | import android.content.res.Resources; 4 | import android.graphics.Bitmap; 5 | import android.graphics.drawable.BitmapDrawable; 6 | 7 | /** 8 | * Created by codeest on 2017/2/4. 9 | */ 10 | 11 | public class RecyclingBitmapDrawable extends BitmapDrawable { 12 | 13 | private int mCacheRefCount = 0; 14 | private int mDisplayRefCount = 0; 15 | 16 | private boolean mHasBeenDisplayed; 17 | 18 | public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) { 19 | super(res, bitmap); 20 | } 21 | 22 | public void setIsDisplayed(boolean isDisplayed) { 23 | synchronized (this) { 24 | if (isDisplayed) { 25 | mDisplayRefCount++; 26 | mHasBeenDisplayed = true; 27 | } else { 28 | mDisplayRefCount--; 29 | } 30 | } 31 | 32 | checkState(); 33 | } 34 | 35 | public void setIsCached(boolean isCached) { 36 | synchronized (this) { 37 | if (isCached) { 38 | mCacheRefCount++; 39 | } else { 40 | mCacheRefCount--; 41 | } 42 | } 43 | 44 | checkState(); 45 | } 46 | 47 | private synchronized void checkState() { 48 | if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed 49 | && hasValidBitmap()) { 50 | getBitmap().recycle(); 51 | } 52 | } 53 | 54 | private synchronized boolean hasValidBitmap() { 55 | Bitmap bitmap = getBitmap(); 56 | return bitmap != null && !bitmap.isRecycled(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/util/DimenUtils.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.util; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.view.WindowManager; 6 | 7 | /** 8 | * Created by codeest on 2017/1/21. 9 | */ 10 | 11 | public class DimenUtils { 12 | 13 | public static int getScreenWidth(Context context) { 14 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 15 | DisplayMetrics outMetrics = new DisplayMetrics(); 16 | wm.getDefaultDisplay().getMetrics(outMetrics); 17 | return outMetrics.widthPixels; 18 | } 19 | 20 | public static int getScreenHeight(Context context) { 21 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 22 | DisplayMetrics outMetrics = new DisplayMetrics(); 23 | wm.getDefaultDisplay().getMetrics(outMetrics); 24 | return outMetrics.heightPixels; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/util/FastBlur.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.util; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | /** 6 | * Created by codeest on 2017/1/18. 7 | */ 8 | 9 | public class FastBlur { 10 | 11 | public static Bitmap blur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) { 12 | 13 | // Stack Blur v1.0 from 14 | // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html 15 | // 16 | // Java Author: Mario Klingemann 17 | // http://incubator.quasimondo.com 18 | // created Feburary 29, 2004 19 | // Android port : Yahel Bouaziz 20 | // http://www.kayenko.com 21 | // ported april 5th, 2012 22 | 23 | // This is a compromise between Gaussian Blur and Box blur 24 | // It creates much better looking blurs than Box Blur, but is 25 | // 7x faster than my Gaussian Blur implementation. 26 | // 27 | // I called it Stack Blur because this describes best how this 28 | // filter works internally: it creates a kind of moving stack 29 | // of colors whilst scanning through the image. Thereby it 30 | // just has to add one new block of color to the right side 31 | // of the stack and remove the leftmost color. The remaining 32 | // colors on the topmost layer of the stack are either added on 33 | // or reduced by one, depending on if they are on the right or 34 | // on the left side of the stack. 35 | // 36 | // If you are using this algorithm in your code please add 37 | // the following line: 38 | // 39 | // Stack Blur Algorithm by Mario Klingemann 40 | 41 | Bitmap bitmap; 42 | if (canReuseInBitmap) { 43 | bitmap = sentBitmap; 44 | } else { 45 | bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); 46 | } 47 | 48 | if (radius < 1) { 49 | return (null); 50 | } 51 | 52 | int w = bitmap.getWidth(); 53 | int h = bitmap.getHeight(); 54 | 55 | int[] pix = new int[w * h]; 56 | bitmap.getPixels(pix, 0, w, 0, 0, w, h); 57 | 58 | int wm = w - 1; 59 | int hm = h - 1; 60 | int wh = w * h; 61 | int div = radius + radius + 1; 62 | 63 | int r[] = new int[wh]; 64 | int g[] = new int[wh]; 65 | int b[] = new int[wh]; 66 | int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; 67 | int vmin[] = new int[Math.max(w, h)]; 68 | 69 | int divsum = (div + 1) >> 1; 70 | divsum *= divsum; 71 | int dv[] = new int[256 * divsum]; 72 | for (i = 0; i < 256 * divsum; i++) { 73 | dv[i] = (i / divsum); 74 | } 75 | 76 | yw = yi = 0; 77 | 78 | int[][] stack = new int[div][3]; 79 | int stackpointer; 80 | int stackstart; 81 | int[] sir; 82 | int rbs; 83 | int r1 = radius + 1; 84 | int routsum, goutsum, boutsum; 85 | int rinsum, ginsum, binsum; 86 | 87 | for (y = 0; y < h; y++) { 88 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; 89 | for (i = -radius; i <= radius; i++) { 90 | p = pix[yi + Math.min(wm, Math.max(i, 0))]; 91 | sir = stack[i + radius]; 92 | sir[0] = (p & 0xff0000) >> 16; 93 | sir[1] = (p & 0x00ff00) >> 8; 94 | sir[2] = (p & 0x0000ff); 95 | rbs = r1 - Math.abs(i); 96 | rsum += sir[0] * rbs; 97 | gsum += sir[1] * rbs; 98 | bsum += sir[2] * rbs; 99 | if (i > 0) { 100 | rinsum += sir[0]; 101 | ginsum += sir[1]; 102 | binsum += sir[2]; 103 | } else { 104 | routsum += sir[0]; 105 | goutsum += sir[1]; 106 | boutsum += sir[2]; 107 | } 108 | } 109 | stackpointer = radius; 110 | 111 | for (x = 0; x < w; x++) { 112 | 113 | r[yi] = dv[rsum]; 114 | g[yi] = dv[gsum]; 115 | b[yi] = dv[bsum]; 116 | 117 | rsum -= routsum; 118 | gsum -= goutsum; 119 | bsum -= boutsum; 120 | 121 | stackstart = stackpointer - radius + div; 122 | sir = stack[stackstart % div]; 123 | 124 | routsum -= sir[0]; 125 | goutsum -= sir[1]; 126 | boutsum -= sir[2]; 127 | 128 | if (y == 0) { 129 | vmin[x] = Math.min(x + radius + 1, wm); 130 | } 131 | p = pix[yw + vmin[x]]; 132 | 133 | sir[0] = (p & 0xff0000) >> 16; 134 | sir[1] = (p & 0x00ff00) >> 8; 135 | sir[2] = (p & 0x0000ff); 136 | 137 | rinsum += sir[0]; 138 | ginsum += sir[1]; 139 | binsum += sir[2]; 140 | 141 | rsum += rinsum; 142 | gsum += ginsum; 143 | bsum += binsum; 144 | 145 | stackpointer = (stackpointer + 1) % div; 146 | sir = stack[(stackpointer) % div]; 147 | 148 | routsum += sir[0]; 149 | goutsum += sir[1]; 150 | boutsum += sir[2]; 151 | 152 | rinsum -= sir[0]; 153 | ginsum -= sir[1]; 154 | binsum -= sir[2]; 155 | 156 | yi++; 157 | } 158 | yw += w; 159 | } 160 | for (x = 0; x < w; x++) { 161 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; 162 | yp = -radius * w; 163 | for (i = -radius; i <= radius; i++) { 164 | yi = Math.max(0, yp) + x; 165 | 166 | sir = stack[i + radius]; 167 | 168 | sir[0] = r[yi]; 169 | sir[1] = g[yi]; 170 | sir[2] = b[yi]; 171 | 172 | rbs = r1 - Math.abs(i); 173 | 174 | rsum += r[yi] * rbs; 175 | gsum += g[yi] * rbs; 176 | bsum += b[yi] * rbs; 177 | 178 | if (i > 0) { 179 | rinsum += sir[0]; 180 | ginsum += sir[1]; 181 | binsum += sir[2]; 182 | } else { 183 | routsum += sir[0]; 184 | goutsum += sir[1]; 185 | boutsum += sir[2]; 186 | } 187 | 188 | if (i < hm) { 189 | yp += w; 190 | } 191 | } 192 | yi = x; 193 | stackpointer = radius; 194 | for (y = 0; y < h; y++) { 195 | // Preserve alpha channel: ( 0xff000000 & pix[yi] ) 196 | pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; 197 | 198 | rsum -= routsum; 199 | gsum -= goutsum; 200 | bsum -= boutsum; 201 | 202 | stackstart = stackpointer - radius + div; 203 | sir = stack[stackstart % div]; 204 | 205 | routsum -= sir[0]; 206 | goutsum -= sir[1]; 207 | boutsum -= sir[2]; 208 | 209 | if (x == 0) { 210 | vmin[y] = Math.min(y + r1, hm) * w; 211 | } 212 | p = x + vmin[y]; 213 | 214 | sir[0] = r[p]; 215 | sir[1] = g[p]; 216 | sir[2] = b[p]; 217 | 218 | rinsum += sir[0]; 219 | ginsum += sir[1]; 220 | binsum += sir[2]; 221 | 222 | rsum += rinsum; 223 | gsum += ginsum; 224 | bsum += binsum; 225 | 226 | stackpointer = (stackpointer + 1) % div; 227 | sir = stack[stackpointer]; 228 | 229 | routsum += sir[0]; 230 | goutsum += sir[1]; 231 | boutsum += sir[2]; 232 | 233 | rinsum -= sir[0]; 234 | ginsum -= sir[1]; 235 | binsum -= sir[2]; 236 | 237 | yi += w; 238 | } 239 | } 240 | 241 | bitmap.setPixels(pix, 0, w, 0, 0, w, h); 242 | 243 | return (bitmap); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /ecardflow/src/main/java/moe/codeest/ecardflow/util/RSBlur.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow.util; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.os.Build; 7 | import android.renderscript.Allocation; 8 | import android.renderscript.Element; 9 | import android.renderscript.RSRuntimeException; 10 | import android.renderscript.RenderScript; 11 | import android.renderscript.ScriptIntrinsicBlur; 12 | 13 | /** 14 | * Created by codeest on 2017/1/18. 15 | */ 16 | 17 | public class RSBlur { 18 | 19 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) 20 | public static Bitmap blur(Context context, Bitmap bitmap, int radius) throws RSRuntimeException { 21 | RenderScript rs = null; 22 | try { 23 | rs = RenderScript.create(context); 24 | rs.setMessageHandler(new RenderScript.RSMessageHandler()); 25 | Allocation input = 26 | Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, 27 | Allocation.USAGE_SCRIPT); 28 | Allocation output = Allocation.createTyped(rs, input.getType()); 29 | ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); 30 | 31 | blur.setInput(input); 32 | blur.setRadius(radius); 33 | blur.forEach(output); 34 | output.copyTo(bitmap); 35 | } finally { 36 | if (rs != null) { 37 | rs.destroy(); 38 | } 39 | } 40 | 41 | return bitmap; 42 | } 43 | } -------------------------------------------------------------------------------- /ecardflow/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ecardflow/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ECardFlow 3 | 4 | -------------------------------------------------------------------------------- /ecardflow/src/test/java/moe/codeest/ecardflow/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package moe.codeest.ecardflow; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /preview/GIF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/preview/GIF.gif -------------------------------------------------------------------------------- /preview/GIFBlur.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/preview/GIFBlur.gif -------------------------------------------------------------------------------- /preview/GIFMove.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeestX/ECardFlow/b04568b9742de60ae1ddfc507c3995e168202716/preview/GIFMove.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':ecardflow' 2 | --------------------------------------------------------------------------------