├── .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/#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 | 
17 |
18 | ECardFlowLayout:
19 |
20 | 
21 | 
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 |
--------------------------------------------------------------------------------