├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── styles.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── layout │ │ │ │ ├── view_recyclerview.xml │ │ │ │ ├── content_main.xml │ │ │ │ └── activity_main.xml │ │ │ └── values-v21 │ │ │ │ └── styles.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── me │ │ │ └── undownding │ │ │ └── baseui │ │ │ └── MainActivity.java │ ├── test │ │ └── java │ │ │ └── me │ │ │ └── undownding │ │ │ └── baseui │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── me │ │ └── undownding │ │ └── baseui │ │ └── ApplicationTest.java ├── proguard-rules.pro └── build.gradle ├── library ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── styles_swipeback.xml │ │ │ │ ├── attrs.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── attrs_swipeback.xml │ │ │ │ └── themes.xml │ │ │ ├── drawable │ │ │ │ └── bottom_shadow.9.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── shadow_left.png │ │ │ │ ├── shadow_bottom.png │ │ │ │ └── shadow_right.png │ │ │ ├── values-v21 │ │ │ │ ├── styles.xml │ │ │ │ └── themes.xml │ │ │ ├── layout │ │ │ │ ├── swipeback_layout.xml │ │ │ │ └── activity_very_base.xml │ │ │ ├── values-v19 │ │ │ │ └── themes.xml │ │ │ ├── layout-v21 │ │ │ │ └── activity_very_base.xml │ │ │ └── layout-v19 │ │ │ │ └── activity_very_base.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ ├── me │ │ │ ├── imid │ │ │ │ └── swipebacklayout │ │ │ │ │ └── lib │ │ │ │ │ ├── app │ │ │ │ │ ├── SwipeBackActivityBase.java │ │ │ │ │ ├── SwipeBackPreferenceActivity.java │ │ │ │ │ ├── SwipeBackActivity.java │ │ │ │ │ └── SwipeBackActivityHelper.java │ │ │ │ │ ├── Utils.java │ │ │ │ │ ├── SwipeBackLayout.java │ │ │ │ │ └── ViewDragHelper.java │ │ │ └── undownding │ │ │ │ └── baseui │ │ │ │ └── library │ │ │ │ ├── NoFitLinearLayout.java │ │ │ │ ├── VeryBaseActivity.java │ │ │ │ └── ShadowView.java │ │ │ └── moe │ │ │ └── feng │ │ │ └── material │ │ │ └── statusbar │ │ │ ├── StatusBarCompat.java │ │ │ ├── TranslucentSBActivity.java │ │ │ ├── util │ │ │ └── ViewHelper.java │ │ │ ├── StatusBarHeaderView.java │ │ │ └── AppBarLayout.java │ ├── test │ │ └── java │ │ │ └── me │ │ │ └── undownding │ │ │ └── baseui │ │ │ └── library │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── me │ │ └── undownding │ │ └── baseui │ │ └── library │ │ └── ApplicationTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | baseui 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /library/src/main/res/drawable/bottom_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/library/src/main/res/drawable/bottom_shadow.9.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/shadow_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/library/src/main/res/drawable-xhdpi/shadow_left.png -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | baseui 3 | Settings 4 | 5 | -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/shadow_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/library/src/main/res/drawable-xhdpi/shadow_bottom.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/shadow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undownding/very-base-activity/HEAD/library/src/main/res/drawable-xhdpi/shadow_right.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /library/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Nov 10 03:53:00 CST 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.4-all.zip 7 | -------------------------------------------------------------------------------- /library/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /library/src/main/res/layout/swipeback_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/test/java/me/undownding/baseui/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package me.undownding.baseui; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/view_recyclerview.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/res/values/styles_swipeback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /library/src/test/java/me/undownding/baseui/library/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package me.undownding.baseui.library; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/undownding/baseui/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package me.undownding.baseui; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /library/src/androidTest/java/me/undownding/baseui/library/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package me.undownding.baseui.library; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /library/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 9 | 10 | 10 | 15 | 7 | 8 | 11 | 12 | 16 | 17 | 23 | 24 | -------------------------------------------------------------------------------- /library/src/main/res/values-v21/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | 16 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # very-base-activity 2 | 3 | [![Jit Pack](https://img.shields.io/github/tag/undownding/very-base-activity.svg?label=JitPack)](https://jitpack.io/#undownding/very-base-activity/0.1) 4 | 5 | An activity to solve StatusBar color, Toolbar shadow(or Toolbar elevation), and swipe back in all Android version. 6 | 7 | ### Useage: 8 | 9 | #### Modify your build.gradle: 10 | 11 | ```groovy 12 | repositories { 13 | // ... 14 | maven { url "https://jitpack.io" } 15 | } 16 | 17 | dependencies { 18 | compile 'com.github.undownding:very-base-activity:0.1' 19 | } 20 | ``` 21 | 22 | #### Use custom theme for target activity 23 | 24 | ```xml 25 | 31 | 35 | 36 | 37 | ``` 38 | 39 | #### Change Activity's parent class 40 | 41 | ```java 42 | public class MainActivity extends VeryBaseActivity { 43 | ... 44 | ``` 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | group='com.github.undownding' 5 | 6 | android { 7 | compileSdkVersion 23 8 | buildToolsVersion "23.0.2" 9 | 10 | defaultConfig { 11 | minSdkVersion 11 12 | targetSdkVersion 23 13 | versionCode 1 14 | versionName "0.1" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | testCompile 'junit:junit:4.12' 27 | compile 'com.android.support:appcompat-v7:23.1.0' 28 | compile 'com.android.support:design:23.1.0' 29 | } 30 | 31 | // build a jar with source files 32 | task sourcesJar(type: Jar) { 33 | from android.sourceSets.main.java.srcDirs 34 | classifier = 'sources' 35 | } 36 | 37 | task javadoc(type: Javadoc) { 38 | failOnError false 39 | source = android.sourceSets.main.java.sourceFiles 40 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 41 | } 42 | 43 | // build a jar with javadoc 44 | task javadocJar(type: Jar, dependsOn: javadoc) { 45 | classifier = 'javadoc' 46 | from javadoc.destinationDir 47 | } 48 | 49 | artifacts { 50 | archives sourcesJar 51 | archives javadocJar 52 | } -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackPreferenceActivity.java: -------------------------------------------------------------------------------- 1 | 2 | package me.imid.swipebacklayout.lib.app; 3 | 4 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 5 | import android.os.Bundle; 6 | import android.preference.PreferenceActivity; 7 | import android.view.View; 8 | 9 | public class SwipeBackPreferenceActivity extends PreferenceActivity implements SwipeBackActivityBase { 10 | private SwipeBackActivityHelper mHelper; 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | mHelper = new SwipeBackActivityHelper(this); 16 | mHelper.onActivityCreate(); 17 | } 18 | 19 | @Override 20 | protected void onPostCreate(Bundle savedInstanceState) { 21 | super.onPostCreate(savedInstanceState); 22 | mHelper.onPostCreate(); 23 | } 24 | 25 | @Override 26 | public View findViewById(int id) { 27 | View v = super.findViewById(id); 28 | if (v == null && mHelper != null) 29 | return mHelper.findViewById(id); 30 | return v; 31 | } 32 | 33 | @Override 34 | public SwipeBackLayout getSwipeBackLayout() { 35 | return mHelper.getSwipeBackLayout(); 36 | } 37 | @Override 38 | public void setSwipeBackEnable(boolean enable) { 39 | getSwipeBackLayout().setEnableGesture(enable); 40 | } 41 | 42 | @Override 43 | public void scrollToFinishActivity() { 44 | getSwipeBackLayout().scrollToFinishActivity(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivity.java: -------------------------------------------------------------------------------- 1 | 2 | package me.imid.swipebacklayout.lib.app; 3 | 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 9 | import me.imid.swipebacklayout.lib.Utils; 10 | 11 | public class SwipeBackActivity extends AppCompatActivity implements SwipeBackActivityBase { 12 | private SwipeBackActivityHelper mHelper; 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | mHelper = new SwipeBackActivityHelper(this); 18 | mHelper.onActivityCreate(); 19 | } 20 | 21 | @Override 22 | protected void onPostCreate(Bundle savedInstanceState) { 23 | super.onPostCreate(savedInstanceState); 24 | mHelper.onPostCreate(); 25 | } 26 | 27 | @Override 28 | public View findViewById(int id) { 29 | View v = super.findViewById(id); 30 | if (v == null && mHelper != null) 31 | return mHelper.findViewById(id); 32 | return v; 33 | } 34 | 35 | @Override 36 | public SwipeBackLayout getSwipeBackLayout() { 37 | return mHelper.getSwipeBackLayout(); 38 | } 39 | 40 | @Override 41 | public void setSwipeBackEnable(boolean enable) { 42 | getSwipeBackLayout().setEnableGesture(enable); 43 | } 44 | 45 | @Override 46 | public void scrollToFinishActivity() { 47 | Utils.convertActivityToTranslucent(this); 48 | getSwipeBackLayout().scrollToFinishActivity(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /library/src/main/java/me/undownding/baseui/library/NoFitLinearLayout.java: -------------------------------------------------------------------------------- 1 | package me.undownding.baseui.library; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Rect; 6 | import android.os.Build; 7 | import android.util.AttributeSet; 8 | import android.widget.LinearLayout; 9 | 10 | /** 11 | * Created by dante on 15-7-24. 12 | */ 13 | public class NoFitLinearLayout extends LinearLayout { 14 | private int[] mInsets = new int[4]; 15 | 16 | public NoFitLinearLayout(Context context) { 17 | super(context); 18 | } 19 | 20 | public NoFitLinearLayout(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | } 23 | 24 | public NoFitLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { 25 | super(context, attrs, defStyleAttr); 26 | } 27 | 28 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 29 | public NoFitLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 30 | super(context, attrs, defStyleAttr, defStyleRes); 31 | } 32 | 33 | @Override 34 | protected boolean fitSystemWindows(Rect insets) { 35 | 36 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 37 | // Intentionally do not modify the bottom inset. For some reason, 38 | // if the bottom inset is modified, window resizing stops working. 39 | // TODO: Figure out why. 40 | 41 | mInsets[0] = insets.left; 42 | mInsets[1] = insets.top; 43 | mInsets[2] = insets.right; 44 | 45 | insets.left = 0; 46 | insets.top = 0; 47 | insets.right = 0; 48 | } 49 | 50 | return super.fitSystemWindows(insets); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 38 | 43 | -------------------------------------------------------------------------------- /library/src/main/java/moe/feng/material/statusbar/util/ViewHelper.java: -------------------------------------------------------------------------------- 1 | package moe.feng.material.statusbar.util; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.os.Build; 6 | import android.util.TypedValue; 7 | 8 | public class ViewHelper { 9 | 10 | public static boolean isChrome(){ 11 | return Build.BRAND.equals("chromium") || Build.BRAND.equals("chrome"); 12 | } 13 | 14 | public static int getStatusBarHeight(Context context) { 15 | int result = 0; 16 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 17 | if (resourceId > 0) { 18 | result = context.getResources().getDimensionPixelSize(resourceId); 19 | } 20 | return result; 21 | } 22 | 23 | private static int getMiddleValue(int prev, int next, float factor) { 24 | return Math.round(prev + (next - prev) * factor); 25 | } 26 | 27 | public static int getMiddleColor(int prevColor, int curColor, float factor) { 28 | if (prevColor == curColor) { 29 | return curColor; 30 | }; 31 | 32 | if (factor == 0f) { 33 | return prevColor; 34 | } else if(factor == 1f) { 35 | return curColor; 36 | } 37 | 38 | int a = getMiddleValue(Color.alpha(prevColor), Color.alpha(curColor), factor); 39 | int r = getMiddleValue(Color.red(prevColor), Color.red(curColor), factor); 40 | int g = getMiddleValue(Color.green(prevColor), Color.green(curColor), factor); 41 | int b = getMiddleValue(Color.blue(prevColor), Color.blue(curColor), factor); 42 | 43 | return Color.argb(a, r, g, b); 44 | } 45 | 46 | public static int getColor(int baseColor, float alphaPercent) { 47 | int alpha = Math.round(Color.alpha(baseColor) * alphaPercent); 48 | 49 | return (baseColor & 0x00FFFFFF) | (alpha << 24); 50 | } 51 | 52 | public static float dpToPx(Context context, float dp) { 53 | return TypedValue.applyDimension( 54 | TypedValue.COMPLEX_UNIT_DIP, 55 | dp, 56 | context.getResources().getDisplayMetrics() 57 | ) + 0.5f; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /library/src/main/res/layout-v21/activity_very_base.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 20 | 21 | 24 | 25 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityHelper.java: -------------------------------------------------------------------------------- 1 | package me.imid.swipebacklayout.lib.app; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | 9 | import me.imid.swipebacklayout.lib.SwipeBackLayout; 10 | import me.imid.swipebacklayout.lib.Utils; 11 | import me.undownding.baseui.library.R; 12 | 13 | /** 14 | * @author Yrom 15 | */ 16 | public class SwipeBackActivityHelper { 17 | private Activity mActivity; 18 | 19 | private SwipeBackLayout mSwipeBackLayout; 20 | 21 | public SwipeBackActivityHelper(Activity activity) { 22 | mActivity = activity; 23 | } 24 | 25 | @SuppressWarnings("deprecation") 26 | public void onActivityCreate() { 27 | mActivity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 28 | mActivity.getWindow().getDecorView().setBackgroundDrawable(null); 29 | mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate( 30 | R.layout.swipeback_layout, null, false); 31 | mSwipeBackLayout.addSwipeListener(new SwipeBackLayout.SwipeListener() { 32 | @Override 33 | public void onScrollStateChange(int state, float scrollPercent) { 34 | } 35 | 36 | @Override 37 | public void onEdgeTouch(int edgeFlag) { 38 | Utils.convertActivityToTranslucent(mActivity); 39 | } 40 | 41 | @Override 42 | public void onScrollOverThreshold() { 43 | 44 | } 45 | }); 46 | } 47 | 48 | public void onPostCreate() { 49 | mSwipeBackLayout.attachToActivity(mActivity); 50 | } 51 | 52 | public View findViewById(int id) { 53 | if (mSwipeBackLayout != null) { 54 | return mSwipeBackLayout.findViewById(id); 55 | } 56 | return null; 57 | } 58 | 59 | public SwipeBackLayout getSwipeBackLayout() { 60 | return mSwipeBackLayout; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /library/src/main/res/layout/activity_very_base.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 17 | 18 | 23 | 24 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /library/src/main/res/layout-v19/activity_very_base.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 20 | 21 | 24 | 25 | 30 | 31 | 38 | 39 | 40 | 41 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/Utils.java: -------------------------------------------------------------------------------- 1 | 2 | package me.imid.swipebacklayout.lib; 3 | 4 | import android.app.Activity; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * Created by Chaojun Wang on 6/9/14. 10 | */ 11 | public class Utils { 12 | private Utils() { 13 | } 14 | 15 | /** 16 | * Convert a translucent themed Activity 17 | * {@link android.R.attr#windowIsTranslucent} to a fullscreen opaque 18 | * Activity. 19 | *

20 | * Call this whenever the background of a translucent Activity has changed 21 | * to become opaque. Doing so will allow the {@link android.view.Surface} of 22 | * the Activity behind to be released. 23 | *

24 | * This call has no effect on non-translucent activities or on activities 25 | * with the {@link android.R.attr#windowIsFloating} attribute. 26 | */ 27 | public static void convertActivityFromTranslucent(Activity activity) { 28 | try { 29 | Method method = Activity.class.getDeclaredMethod("convertFromTranslucent"); 30 | method.setAccessible(true); 31 | method.invoke(activity); 32 | } catch (Throwable t) { 33 | } 34 | } 35 | 36 | /** 37 | * Convert a translucent themed Activity 38 | * {@link android.R.attr#windowIsTranslucent} back from opaque to 39 | * translucent following a call to 40 | * {@link #convertActivityFromTranslucent(android.app.Activity)} . 41 | *

42 | * Calling this allows the Activity behind this one to be seen again. Once 43 | * all such Activities have been redrawn 44 | *

45 | * This call has no effect on non-translucent activities or on activities 46 | * with the {@link android.R.attr#windowIsFloating} attribute. 47 | */ 48 | public static void convertActivityToTranslucent(Activity activity) { 49 | try { 50 | Class[] classes = Activity.class.getDeclaredClasses(); 51 | Class translucentConversionListenerClazz = null; 52 | for (Class clazz : classes) { 53 | if (clazz.getSimpleName().contains("TranslucentConversionListener")) { 54 | translucentConversionListenerClazz = clazz; 55 | } 56 | } 57 | Method method = Activity.class.getDeclaredMethod("convertToTranslucent", 58 | translucentConversionListenerClazz); 59 | method.setAccessible(true); 60 | method.invoke(activity, new Object[] { 61 | null 62 | }); 63 | } catch (Throwable t) { 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/src/main/java/me/undownding/baseui/library/VeryBaseActivity.java: -------------------------------------------------------------------------------- 1 | package me.undownding.baseui.library; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.support.design.widget.AppBarLayout; 6 | import android.support.v4.widget.NestedScrollView; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | 12 | import moe.feng.material.statusbar.TranslucentSBActivity; 13 | import moe.feng.material.statusbar.util.ViewHelper; 14 | 15 | /** 16 | * Created by undownding on 15-11-10. 17 | */ 18 | public class VeryBaseActivity extends TranslucentSBActivity{ 19 | 20 | private boolean toolbarAutoHidden; 21 | private Toolbar toolbar; 22 | private FrameLayout content; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | super.setContentView(R.layout.activity_very_base); 28 | toolbar = (Toolbar) findViewById(R.id.toolbar); 29 | content = (FrameLayout) findViewById(R.id.verybase_content); 30 | 31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 32 | View view = findViewById(R.id.status_bar_view); 33 | if (ViewHelper.isChrome()) { 34 | view.setVisibility(View.GONE); 35 | } else { 36 | view.getLayoutParams().height = ViewHelper.getStatusBarHeight(this); 37 | view.invalidate(); 38 | } 39 | } 40 | } 41 | 42 | public boolean isToolbarAutoHidden() { 43 | return toolbarAutoHidden; 44 | } 45 | 46 | public void setToolbarAutoHidden(boolean toolbarAutoHidden) { 47 | this.toolbarAutoHidden = toolbarAutoHidden; 48 | AppBarLayout.LayoutParams params = 49 | (AppBarLayout.LayoutParams) toolbar.getLayoutParams(); 50 | if (isToolbarAutoHidden()) { 51 | params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL 52 | | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS); 53 | } else { 54 | params.setScrollFlags(0); 55 | } 56 | toolbar.invalidate(); 57 | } 58 | 59 | @Override 60 | public final void setContentView(int layoutResID) { 61 | final View view = getLayoutInflater().inflate(layoutResID, content, false); 62 | setContentView(view); 63 | } 64 | 65 | @Override 66 | public final void setContentView(View view) { 67 | setContentView(view, new NestedScrollView.LayoutParams( 68 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) 69 | ); 70 | } 71 | 72 | @Override 73 | public final void setContentView(View view, ViewGroup.LayoutParams params) { 74 | content.addView(view, params); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /library/src/main/java/me/undownding/baseui/library/ShadowView.java: -------------------------------------------------------------------------------- 1 | package me.undownding.baseui.library; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.support.v4.view.NestedScrollingChild; 7 | import android.support.v4.view.NestedScrollingChildHelper; 8 | import android.util.AttributeSet; 9 | import android.widget.FrameLayout; 10 | 11 | /** 12 | * Created by undownding on 15-11-10. 13 | */ 14 | public class ShadowView extends FrameLayout implements NestedScrollingChild { 15 | 16 | private final NestedScrollingChildHelper mChildHelper = new NestedScrollingChildHelper(this); 17 | 18 | public ShadowView(Context context) { 19 | super(context); 20 | } 21 | 22 | public ShadowView(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | } 25 | 26 | public ShadowView(Context context, AttributeSet attrs, int defStyleAttr) { 27 | super(context, attrs, defStyleAttr); 28 | } 29 | 30 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 31 | public ShadowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 32 | super(context, attrs, defStyleAttr, defStyleRes); 33 | } 34 | 35 | // NestedScrollingChild 36 | 37 | @Override 38 | public void setNestedScrollingEnabled(boolean enabled) { 39 | mChildHelper.setNestedScrollingEnabled(enabled); 40 | } 41 | 42 | @Override 43 | public boolean isNestedScrollingEnabled() { 44 | return mChildHelper.isNestedScrollingEnabled(); 45 | } 46 | 47 | @Override 48 | public boolean startNestedScroll(int axes) { 49 | return mChildHelper.startNestedScroll(axes); 50 | } 51 | 52 | @Override 53 | public void stopNestedScroll() { 54 | mChildHelper.stopNestedScroll(); 55 | } 56 | 57 | @Override 58 | public boolean hasNestedScrollingParent() { 59 | return mChildHelper.hasNestedScrollingParent(); 60 | } 61 | 62 | @Override 63 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 64 | int dyUnconsumed, int[] offsetInWindow) { 65 | return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 66 | offsetInWindow); 67 | } 68 | 69 | @Override 70 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 71 | return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 72 | } 73 | 74 | @Override 75 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 76 | return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 77 | } 78 | 79 | @Override 80 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 81 | return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /library/src/main/java/moe/feng/material/statusbar/StatusBarHeaderView.java: -------------------------------------------------------------------------------- 1 | package moe.feng.material.statusbar; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.os.Build; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import me.undownding.baseui.library.R; 12 | import moe.feng.material.statusbar.util.ViewHelper; 13 | 14 | public class StatusBarHeaderView extends View { 15 | 16 | private int colorNormal, colorDark, enableMode; 17 | 18 | public static final int MODE_KITKAT = 1, MODE_LOLLIPOP = 2, MODE_ALL = 3; 19 | 20 | public StatusBarHeaderView(Context context) { 21 | this(context, null); 22 | } 23 | 24 | public StatusBarHeaderView(Context context, AttributeSet attrs) { 25 | this(context, attrs, 0); 26 | } 27 | 28 | public StatusBarHeaderView(Context context, AttributeSet attrs, int defStyle) { 29 | super(context, attrs, defStyle); 30 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StatusBarHeaderView, defStyle, 31 | R.style.Widget_FengMoe_StatusBarHeaderView); 32 | colorNormal = a.getColor(R.styleable.StatusBarHeaderView_colorNormal, Color.TRANSPARENT); 33 | if (a.hasValue(R.styleable.StatusBarHeaderView_colorDark)) { 34 | colorDark = a.getColor(R.styleable.StatusBarHeaderView_colorDark, Color.TRANSPARENT); 35 | } else { 36 | colorDark = ViewHelper.getMiddleColor(colorNormal, Color.BLACK, 0.2f); 37 | } 38 | enableMode = a.getInt(R.styleable.StatusBarHeaderView_enableMode, MODE_ALL); 39 | init(); 40 | a.recycle(); 41 | } 42 | 43 | public StatusBarHeaderView(Context context, int colorNormal, int colorDark, int enableMode) { 44 | this(context); 45 | this.colorNormal = colorNormal; 46 | this.colorDark = colorDark; 47 | this.enableMode = enableMode; 48 | init(); 49 | } 50 | 51 | @Override 52 | public void onMeasure(int widthSpec, int heightSpec) { 53 | super.onMeasure(widthSpec, heightSpec); 54 | // adjustHeight(); 55 | } 56 | 57 | @Override 58 | public void invalidate() { 59 | super.invalidate(); 60 | adjustHeight(); 61 | } 62 | 63 | public void adjustHeight() { 64 | ViewGroup.LayoutParams params = getLayoutParams(); 65 | params.height = ViewHelper.getStatusBarHeight(getContext()); 66 | } 67 | 68 | void init() { 69 | int SDK_INT = Build.VERSION.SDK_INT; 70 | this.setBackgroundColor(SDK_INT == 19 ? colorNormal : colorDark); 71 | this.setVisibility( 72 | !ViewHelper.isChrome() && ( 73 | (((enableMode == MODE_KITKAT) && (SDK_INT == 19)) || 74 | ((enableMode == MODE_LOLLIPOP) && (SDK_INT == 21)) || 75 | ((enableMode == MODE_ALL) && (SDK_INT >= 19))) 76 | ) ? View.VISIBLE : View.GONE 77 | ); 78 | } 79 | 80 | public void setNormalColor(int colorNormal) { 81 | this.colorNormal = colorNormal; 82 | init(); 83 | } 84 | 85 | public void setDarkColor(int colorDark) { 86 | this.colorDark = colorDark; 87 | init(); 88 | } 89 | 90 | public int getNormalColor() { 91 | return this.colorNormal; 92 | } 93 | 94 | public int getDarkColor() { 95 | return this.colorDark; 96 | } 97 | 98 | public void setMode(int mode) { 99 | this.enableMode = mode; 100 | init(); 101 | } 102 | 103 | public int getMode() { 104 | return this.enableMode; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /library/src/main/java/moe/feng/material/statusbar/AppBarLayout.java: -------------------------------------------------------------------------------- 1 | package moe.feng.material.statusbar; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.os.Build; 7 | import android.support.annotation.ColorInt; 8 | import android.support.annotation.ColorRes; 9 | import android.util.AttributeSet; 10 | import android.widget.LinearLayout; 11 | 12 | import me.undownding.baseui.library.R; 13 | import moe.feng.material.statusbar.util.ViewHelper; 14 | 15 | public class AppBarLayout extends LinearLayout { 16 | 17 | private int colorNormal, colorDark, enableMode; 18 | 19 | private StatusBarHeaderView headerView; 20 | 21 | public static final int MODE_KITKAT = 1, MODE_LOLLIPOP = 2, MODE_ALL = 3; 22 | 23 | public AppBarLayout(Context context) { 24 | this(context, null); 25 | } 26 | 27 | public AppBarLayout(Context context, AttributeSet attrs) { 28 | this(context, attrs, 0); 29 | } 30 | 31 | public AppBarLayout(Context context, AttributeSet attrs, int defStyle) { 32 | super(context, attrs, defStyle); 33 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StatusBarHeaderView, defStyle, 34 | R.style.Widget_FengMoe_StatusBarHeaderView); 35 | colorNormal = a.getColor(R.styleable.StatusBarHeaderView_colorNormal, Color.TRANSPARENT); 36 | if (a.hasValue(R.styleable.StatusBarHeaderView_colorDark)) { 37 | colorDark = a.getColor(R.styleable.StatusBarHeaderView_colorDark, Color.TRANSPARENT); 38 | } else { 39 | colorDark = ViewHelper.getMiddleColor(colorNormal, Color.BLACK, 0.2f); 40 | } 41 | enableMode = a.getInt(R.styleable.StatusBarHeaderView_enableMode, MODE_ALL); 42 | headerView = new StatusBarHeaderView(context, colorNormal, colorDark, enableMode); 43 | this.setBackgroundColorWithoutAlpha(colorNormal); 44 | this.setOrientation(LinearLayout.VERTICAL); 45 | this.addView(headerView); 46 | a.recycle(); 47 | if (Build.VERSION.SDK_INT >= 21) { 48 | this.setElevation(ViewHelper.dpToPx(context, 5f)); 49 | } 50 | } 51 | 52 | public void setNormalColor(@ColorInt int colorNormal) { 53 | this.colorNormal = colorNormal; 54 | this.setBackgroundColorWithoutAlpha(colorNormal); 55 | headerView.setNormalColor(colorNormal); 56 | headerView.init(); 57 | } 58 | 59 | public void setDarkColor(@ColorInt int colorDark) { 60 | this.colorDark = colorDark; 61 | headerView.setDarkColor(colorDark); 62 | headerView.init(); 63 | } 64 | 65 | public void setColor(@ColorInt int colorNormal,@ColorInt int colorDark) { 66 | this.colorNormal = colorNormal; 67 | this.colorDark = colorDark; 68 | this.setBackgroundColorWithoutAlpha(colorNormal); 69 | headerView.setNormalColor(colorNormal); 70 | headerView.setDarkColor(colorDark); 71 | headerView.init(); 72 | } 73 | 74 | public void setColorResources(@ColorRes int colorNormal, @ColorRes int colorDark) { 75 | this.setColor( 76 | getResources().getColor(colorNormal), 77 | getResources().getColor(colorDark) 78 | ); 79 | } 80 | 81 | public int getNormalColor() { 82 | return this.colorNormal; 83 | } 84 | 85 | public int getDarkColor(){ 86 | return this.colorDark; 87 | } 88 | 89 | public void setMode(int mode) { 90 | this.enableMode = mode; 91 | headerView.setMode(mode); 92 | headerView.init(); 93 | } 94 | 95 | public int getMode(){ 96 | return this.enableMode; 97 | } 98 | 99 | private void setBackgroundColorWithoutAlpha(int color) { 100 | this.setBackgroundColor(Color.argb(255, Color.red(color), Color.green(color), Color.blue(color))); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/me/undownding/baseui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.undownding.baseui; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.support.design.widget.FloatingActionButton; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.Toolbar; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.TextView; 14 | 15 | import me.undownding.baseui.library.VeryBaseActivity; 16 | import moe.feng.material.statusbar.TranslucentSBActivity; 17 | import moe.feng.material.statusbar.util.ViewHelper; 18 | 19 | public class MainActivity extends VeryBaseActivity { 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 25 | setSupportActionBar(toolbar); 26 | 27 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 28 | /*fab.setOnClickListener(new View.OnClickListener() { 29 | @Override 30 | public void onClick(View view) { 31 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 32 | .setAction("Action", null).show(); 33 | } 34 | });*/ 35 | 36 | RecyclerView recyclerView = (RecyclerView) getLayoutInflater().inflate(R.layout.view_recyclerview, null, false); 37 | recyclerView.setAdapter(new RecyclerView.Adapter() { 38 | @Override 39 | public Holder onCreateViewHolder(ViewGroup parent, int viewType) { 40 | return new Holder(new TextView(MainActivity.this)); 41 | } 42 | 43 | @Override 44 | public void onBindViewHolder(Holder holder, int position) { 45 | holder.setText("Line " + (position + 1)); 46 | } 47 | 48 | @Override 49 | public int getItemCount() { 50 | return 40; 51 | } 52 | }); 53 | 54 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 55 | setContentView(recyclerView); 56 | } 57 | 58 | @Override 59 | public boolean onCreateOptionsMenu(Menu menu) { 60 | // Inflate the menu; this adds items to the action bar if it is present. 61 | getMenuInflater().inflate(R.menu.menu_main, menu); 62 | return true; 63 | } 64 | 65 | @Override 66 | public boolean onOptionsItemSelected(MenuItem item) { 67 | // Handle action bar item clicks here. The action bar will 68 | // automatically handle clicks on the Home/Up button, so long 69 | // as you specify a parent activity in AndroidManifest.xml. 70 | int id = item.getItemId(); 71 | 72 | //noinspection SimplifiableIfStatement 73 | if (id == R.id.action_settings) { 74 | return true; 75 | } 76 | 77 | return super.onOptionsItemSelected(item); 78 | } 79 | 80 | private class Holder extends RecyclerView.ViewHolder { 81 | 82 | private String text; 83 | private TextView textView; 84 | 85 | public Holder(TextView itemView) { 86 | super(itemView); 87 | textView = itemView; 88 | } 89 | 90 | public String getText() { 91 | return text; 92 | } 93 | 94 | public void setText(String text) { 95 | this.text = text; 96 | textView.setText(text); 97 | textView.setTextColor(getResources().getColor(android.R.color.black)); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/SwipeBackLayout.java: -------------------------------------------------------------------------------- 1 | package me.imid.swipebacklayout.lib; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Canvas; 7 | import android.graphics.Rect; 8 | import android.graphics.drawable.Drawable; 9 | import android.support.v4.view.ViewCompat; 10 | import android.util.AttributeSet; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.FrameLayout; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import me.undownding.baseui.library.R; 20 | 21 | public class SwipeBackLayout extends FrameLayout { 22 | /** 23 | * Minimum velocity that will be detected as a fling 24 | */ 25 | private static final int MIN_FLING_VELOCITY = 400; // dips per second 26 | 27 | private static final int DEFAULT_SCRIM_COLOR = 0x99000000; 28 | 29 | private static final int FULL_ALPHA = 255; 30 | 31 | /** 32 | * Edge flag indicating that the left edge should be affected. 33 | */ 34 | public static final int EDGE_LEFT = ViewDragHelper.EDGE_LEFT; 35 | 36 | /** 37 | * Edge flag indicating that the right edge should be affected. 38 | */ 39 | public static final int EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT; 40 | 41 | /** 42 | * Edge flag indicating that the bottom edge should be affected. 43 | */ 44 | public static final int EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM; 45 | 46 | /** 47 | * Edge flag set indicating all edges should be affected. 48 | */ 49 | public static final int EDGE_ALL = EDGE_LEFT | EDGE_RIGHT | EDGE_BOTTOM; 50 | 51 | /** 52 | * A view is not currently being dragged or animating as a result of a 53 | * fling/snap. 54 | */ 55 | public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; 56 | 57 | /** 58 | * A view is currently being dragged. The position is currently changing as 59 | * a result of user input or simulated user input. 60 | */ 61 | public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING; 62 | 63 | /** 64 | * A view is currently settling into place as a result of a fling or 65 | * predefined non-interactive motion. 66 | */ 67 | public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; 68 | 69 | /** 70 | * Default threshold of scroll 71 | */ 72 | private static final float DEFAULT_SCROLL_THRESHOLD = 0.3f; 73 | 74 | private static final int OVERSCROLL_DISTANCE = 10; 75 | 76 | private static final int[] EDGE_FLAGS = { 77 | EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL 78 | }; 79 | 80 | private int mEdgeFlag; 81 | 82 | /** 83 | * Threshold of scroll, we will close the activity, when scrollPercent over 84 | * this value; 85 | */ 86 | private float mScrollThreshold = DEFAULT_SCROLL_THRESHOLD; 87 | 88 | private Activity mActivity; 89 | 90 | private boolean mEnable = true; 91 | 92 | private View mContentView; 93 | 94 | private ViewDragHelper mDragHelper; 95 | 96 | private float mScrollPercent; 97 | 98 | private int mContentLeft; 99 | 100 | private int mContentTop; 101 | 102 | /** 103 | * The set of listeners to be sent events through. 104 | */ 105 | private List mListeners; 106 | 107 | private Drawable mShadowLeft; 108 | 109 | private Drawable mShadowRight; 110 | 111 | private Drawable mShadowBottom; 112 | 113 | private float mScrimOpacity; 114 | 115 | private int mScrimColor = DEFAULT_SCRIM_COLOR; 116 | 117 | private boolean mInLayout; 118 | 119 | private Rect mTmpRect = new Rect(); 120 | 121 | /** 122 | * Edge being dragged 123 | */ 124 | private int mTrackingEdge; 125 | 126 | public SwipeBackLayout(Context context) { 127 | this(context, null); 128 | } 129 | 130 | public SwipeBackLayout(Context context, AttributeSet attrs) { 131 | this(context, attrs, R.attr.SwipeBackLayoutStyle); 132 | } 133 | 134 | public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) { 135 | super(context, attrs); 136 | mDragHelper = ViewDragHelper.create(this, new ViewDragCallback()); 137 | 138 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout, defStyle, 139 | R.style.SwipeBackLayout); 140 | 141 | int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edge_size, -1); 142 | if (edgeSize > 0) 143 | setEdgeSize(edgeSize); 144 | int mode = EDGE_FLAGS[a.getInt(R.styleable.SwipeBackLayout_edge_flag, 0)]; 145 | setEdgeTrackingEnabled(mode); 146 | 147 | int shadowLeft = a.getResourceId(R.styleable.SwipeBackLayout_shadow_left, 148 | R.drawable.shadow_left); 149 | int shadowRight = a.getResourceId(R.styleable.SwipeBackLayout_shadow_right, 150 | R.drawable.shadow_right); 151 | int shadowBottom = a.getResourceId(R.styleable.SwipeBackLayout_shadow_bottom, 152 | R.drawable.shadow_bottom); 153 | setShadow(shadowLeft, EDGE_LEFT); 154 | setShadow(shadowRight, EDGE_RIGHT); 155 | setShadow(shadowBottom, EDGE_BOTTOM); 156 | a.recycle(); 157 | final float density = getResources().getDisplayMetrics().density; 158 | final float minVel = MIN_FLING_VELOCITY * density; 159 | mDragHelper.setMinVelocity(minVel); 160 | mDragHelper.setMaxVelocity(minVel * 2f); 161 | } 162 | 163 | /** 164 | * Sets the sensitivity of the NavigationLayout. 165 | * 166 | * @param context The application context. 167 | * @param sensitivity value between 0 and 1, the final value for touchSlop = 168 | * ViewConfiguration.getScaledTouchSlop * (1 / s); 169 | */ 170 | public void setSensitivity(Context context, float sensitivity) { 171 | mDragHelper.setSensitivity(context, sensitivity); 172 | } 173 | 174 | /** 175 | * Set up contentView which will be moved by user gesture 176 | * 177 | * @param view 178 | */ 179 | private void setContentView(View view) { 180 | mContentView = view; 181 | } 182 | 183 | public void setEnableGesture(boolean enable) { 184 | mEnable = enable; 185 | } 186 | 187 | /** 188 | * Enable edge tracking for the selected edges of the parent view. The 189 | * callback's 190 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeTouched(int, int)} 191 | * and 192 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeDragStarted(int, int)} 193 | * methods will only be invoked for edges for which edge tracking has been 194 | * enabled. 195 | * 196 | * @param edgeFlags Combination of edge flags describing the edges to watch 197 | * @see #EDGE_LEFT 198 | * @see #EDGE_RIGHT 199 | * @see #EDGE_BOTTOM 200 | */ 201 | public void setEdgeTrackingEnabled(int edgeFlags) { 202 | mEdgeFlag = edgeFlags; 203 | mDragHelper.setEdgeTrackingEnabled(mEdgeFlag); 204 | } 205 | 206 | /** 207 | * Set a color to use for the scrim that obscures primary content while a 208 | * drawer is open. 209 | * 210 | * @param color Color to use in 0xAARRGGBB format. 211 | */ 212 | public void setScrimColor(int color) { 213 | mScrimColor = color; 214 | invalidate(); 215 | } 216 | 217 | /** 218 | * Set the size of an edge. This is the range in pixels along the edges of 219 | * this view that will actively detect edge touches or drags if edge 220 | * tracking is enabled. 221 | * 222 | * @param size The size of an edge in pixels 223 | */ 224 | public void setEdgeSize(int size) { 225 | mDragHelper.setEdgeSize(size); 226 | } 227 | 228 | /** 229 | * Register a callback to be invoked when a swipe event is sent to this 230 | * view. 231 | * 232 | * @param listener the swipe listener to attach to this view 233 | * @deprecated use {@link #addSwipeListener} instead 234 | */ 235 | @Deprecated 236 | public void setSwipeListener(SwipeListener listener) { 237 | addSwipeListener(listener); 238 | } 239 | 240 | /** 241 | * Add a callback to be invoked when a swipe event is sent to this view. 242 | * 243 | * @param listener the swipe listener to attach to this view 244 | */ 245 | public void addSwipeListener(SwipeListener listener) { 246 | if (mListeners == null) { 247 | mListeners = new ArrayList(); 248 | } 249 | mListeners.add(listener); 250 | } 251 | 252 | /** 253 | * Removes a listener from the set of listeners 254 | * 255 | * @param listener 256 | */ 257 | public void removeSwipeListener(SwipeListener listener) { 258 | if (mListeners == null) { 259 | return; 260 | } 261 | mListeners.remove(listener); 262 | } 263 | 264 | public static interface SwipeListener { 265 | /** 266 | * Invoke when state change 267 | * 268 | * @param state flag to describe scroll state 269 | * @param scrollPercent scroll percent of this view 270 | * @see #STATE_IDLE 271 | * @see #STATE_DRAGGING 272 | * @see #STATE_SETTLING 273 | */ 274 | public void onScrollStateChange(int state, float scrollPercent); 275 | 276 | /** 277 | * Invoke when edge touched 278 | * 279 | * @param edgeFlag edge flag describing the edge being touched 280 | * @see #EDGE_LEFT 281 | * @see #EDGE_RIGHT 282 | * @see #EDGE_BOTTOM 283 | */ 284 | public void onEdgeTouch(int edgeFlag); 285 | 286 | /** 287 | * Invoke when scroll percent over the threshold for the first time 288 | */ 289 | public void onScrollOverThreshold(); 290 | } 291 | 292 | /** 293 | * Set scroll threshold, we will close the activity, when scrollPercent over 294 | * this value 295 | * 296 | * @param threshold 297 | */ 298 | public void setScrollThresHold(float threshold) { 299 | if (threshold >= 1.0f || threshold <= 0) { 300 | throw new IllegalArgumentException("Threshold value should be between 0 and 1.0"); 301 | } 302 | mScrollThreshold = threshold; 303 | } 304 | 305 | /** 306 | * Set a drawable used for edge shadow. 307 | * 308 | * @param shadow Drawable to use 309 | * @param edgeFlag Combination of edge flags describing the edge to set 310 | * @see #EDGE_LEFT 311 | * @see #EDGE_RIGHT 312 | * @see #EDGE_BOTTOM 313 | */ 314 | public void setShadow(Drawable shadow, int edgeFlag) { 315 | if ((edgeFlag & EDGE_LEFT) != 0) { 316 | mShadowLeft = shadow; 317 | } else if ((edgeFlag & EDGE_RIGHT) != 0) { 318 | mShadowRight = shadow; 319 | } else if ((edgeFlag & EDGE_BOTTOM) != 0) { 320 | mShadowBottom = shadow; 321 | } 322 | invalidate(); 323 | } 324 | 325 | /** 326 | * Set a drawable used for edge shadow. 327 | * 328 | * @param resId Resource of drawable to use 329 | * @param edgeFlag Combination of edge flags describing the edge to set 330 | * @see #EDGE_LEFT 331 | * @see #EDGE_RIGHT 332 | * @see #EDGE_BOTTOM 333 | */ 334 | public void setShadow(int resId, int edgeFlag) { 335 | setShadow(getResources().getDrawable(resId), edgeFlag); 336 | } 337 | 338 | /** 339 | * Scroll out contentView and finish the activity 340 | */ 341 | public void scrollToFinishActivity() { 342 | final int childWidth = mContentView.getWidth(); 343 | final int childHeight = mContentView.getHeight(); 344 | 345 | int left = 0, top = 0; 346 | if ((mEdgeFlag & EDGE_LEFT) != 0) { 347 | left = childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE; 348 | mTrackingEdge = EDGE_LEFT; 349 | } else if ((mEdgeFlag & EDGE_RIGHT) != 0) { 350 | left = -childWidth - mShadowRight.getIntrinsicWidth() - OVERSCROLL_DISTANCE; 351 | mTrackingEdge = EDGE_RIGHT; 352 | } else if ((mEdgeFlag & EDGE_BOTTOM) != 0) { 353 | top = -childHeight - mShadowBottom.getIntrinsicHeight() - OVERSCROLL_DISTANCE; 354 | mTrackingEdge = EDGE_BOTTOM; 355 | } 356 | 357 | mDragHelper.smoothSlideViewTo(mContentView, left, top); 358 | invalidate(); 359 | } 360 | 361 | @Override 362 | public boolean onInterceptTouchEvent(MotionEvent event) { 363 | if (!mEnable) { 364 | return false; 365 | } 366 | try { 367 | return mDragHelper.shouldInterceptTouchEvent(event); 368 | } catch (ArrayIndexOutOfBoundsException e) { 369 | // FIXME: handle exception 370 | // issues #9 371 | return false; 372 | } 373 | } 374 | 375 | @Override 376 | public boolean onTouchEvent(MotionEvent event) { 377 | if (!mEnable) { 378 | return false; 379 | } 380 | mDragHelper.processTouchEvent(event); 381 | return true; 382 | } 383 | 384 | @Override 385 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 386 | mInLayout = true; 387 | if (mContentView != null) 388 | mContentView.layout(mContentLeft, mContentTop, 389 | mContentLeft + mContentView.getMeasuredWidth(), 390 | mContentTop + mContentView.getMeasuredHeight()); 391 | mInLayout = false; 392 | } 393 | 394 | @Override 395 | public void requestLayout() { 396 | if (!mInLayout) { 397 | super.requestLayout(); 398 | } 399 | } 400 | 401 | @Override 402 | protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 403 | final boolean drawContent = child == mContentView; 404 | 405 | boolean ret = super.drawChild(canvas, child, drawingTime); 406 | if (mScrimOpacity > 0 && drawContent 407 | && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) { 408 | drawShadow(canvas, child); 409 | drawScrim(canvas, child); 410 | } 411 | return ret; 412 | } 413 | 414 | private void drawScrim(Canvas canvas, View child) { 415 | final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; 416 | final int alpha = (int) (baseAlpha * mScrimOpacity); 417 | final int color = alpha << 24 | (mScrimColor & 0xffffff); 418 | 419 | if ((mTrackingEdge & EDGE_LEFT) != 0) { 420 | canvas.clipRect(0, 0, child.getLeft(), getHeight()); 421 | } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { 422 | canvas.clipRect(child.getRight(), 0, getRight(), getHeight()); 423 | } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) { 424 | canvas.clipRect(child.getLeft(), child.getBottom(), getRight(), getHeight()); 425 | } 426 | canvas.drawColor(color); 427 | } 428 | 429 | private void drawShadow(Canvas canvas, View child) { 430 | final Rect childRect = mTmpRect; 431 | child.getHitRect(childRect); 432 | 433 | if ((mEdgeFlag & EDGE_LEFT) != 0) { 434 | mShadowLeft.setBounds(childRect.left - mShadowLeft.getIntrinsicWidth(), childRect.top, 435 | childRect.left, childRect.bottom); 436 | mShadowLeft.setAlpha((int) (mScrimOpacity * FULL_ALPHA)); 437 | mShadowLeft.draw(canvas); 438 | } 439 | 440 | if ((mEdgeFlag & EDGE_RIGHT) != 0) { 441 | mShadowRight.setBounds(childRect.right, childRect.top, 442 | childRect.right + mShadowRight.getIntrinsicWidth(), childRect.bottom); 443 | mShadowRight.setAlpha((int) (mScrimOpacity * FULL_ALPHA)); 444 | mShadowRight.draw(canvas); 445 | } 446 | 447 | if ((mEdgeFlag & EDGE_BOTTOM) != 0) { 448 | mShadowBottom.setBounds(childRect.left, childRect.bottom, childRect.right, 449 | childRect.bottom + mShadowBottom.getIntrinsicHeight()); 450 | mShadowBottom.setAlpha((int) (mScrimOpacity * FULL_ALPHA)); 451 | mShadowBottom.draw(canvas); 452 | } 453 | } 454 | 455 | public void attachToActivity(Activity activity) { 456 | mActivity = activity; 457 | TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{ 458 | android.R.attr.windowBackground 459 | }); 460 | int background = a.getResourceId(0, 0); 461 | a.recycle(); 462 | 463 | ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView(); 464 | ViewGroup decorChild = (ViewGroup) decor.getChildAt(0); 465 | decorChild.setBackgroundResource(background); 466 | decor.removeView(decorChild); 467 | addView(decorChild); 468 | setContentView(decorChild); 469 | decor.addView(this); 470 | } 471 | 472 | @Override 473 | public void computeScroll() { 474 | mScrimOpacity = 1 - mScrollPercent; 475 | if (mDragHelper.continueSettling(true)) { 476 | ViewCompat.postInvalidateOnAnimation(this); 477 | } 478 | } 479 | 480 | private class ViewDragCallback extends ViewDragHelper.Callback { 481 | private boolean mIsScrollOverValid; 482 | 483 | @Override 484 | public boolean tryCaptureView(View view, int i) { 485 | boolean ret = mDragHelper.isEdgeTouched(mEdgeFlag, i); 486 | if (ret) { 487 | if (mDragHelper.isEdgeTouched(EDGE_LEFT, i)) { 488 | mTrackingEdge = EDGE_LEFT; 489 | } else if (mDragHelper.isEdgeTouched(EDGE_RIGHT, i)) { 490 | mTrackingEdge = EDGE_RIGHT; 491 | } else if (mDragHelper.isEdgeTouched(EDGE_BOTTOM, i)) { 492 | mTrackingEdge = EDGE_BOTTOM; 493 | } 494 | if (mListeners != null && !mListeners.isEmpty()) { 495 | for (SwipeListener listener : mListeners) { 496 | listener.onEdgeTouch(mTrackingEdge); 497 | } 498 | } 499 | mIsScrollOverValid = true; 500 | } 501 | return ret; 502 | } 503 | 504 | @Override 505 | public int getViewHorizontalDragRange(View child) { 506 | return mEdgeFlag & (EDGE_LEFT | EDGE_RIGHT); 507 | } 508 | 509 | @Override 510 | public int getViewVerticalDragRange(View child) { 511 | return mEdgeFlag & EDGE_BOTTOM; 512 | } 513 | 514 | @Override 515 | public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 516 | super.onViewPositionChanged(changedView, left, top, dx, dy); 517 | if ((mTrackingEdge & EDGE_LEFT) != 0) { 518 | mScrollPercent = Math.abs((float) left 519 | / (mContentView.getWidth() + mShadowLeft.getIntrinsicWidth())); 520 | } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { 521 | mScrollPercent = Math.abs((float) left 522 | / (mContentView.getWidth() + mShadowRight.getIntrinsicWidth())); 523 | } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) { 524 | mScrollPercent = Math.abs((float) top 525 | / (mContentView.getHeight() + mShadowBottom.getIntrinsicHeight())); 526 | } 527 | mContentLeft = left; 528 | mContentTop = top; 529 | invalidate(); 530 | if (mScrollPercent < mScrollThreshold && !mIsScrollOverValid) { 531 | mIsScrollOverValid = true; 532 | } 533 | if (mListeners != null && !mListeners.isEmpty() 534 | && mDragHelper.getViewDragState() == STATE_DRAGGING 535 | && mScrollPercent >= mScrollThreshold && mIsScrollOverValid) { 536 | mIsScrollOverValid = false; 537 | for (SwipeListener listener : mListeners) { 538 | listener.onScrollOverThreshold(); 539 | } 540 | } 541 | 542 | if (mScrollPercent >= 1) { 543 | if (!mActivity.isFinishing()) 544 | mActivity.finish(); 545 | } 546 | } 547 | 548 | @Override 549 | public void onViewReleased(View releasedChild, float xvel, float yvel) { 550 | final int childWidth = releasedChild.getWidth(); 551 | final int childHeight = releasedChild.getHeight(); 552 | 553 | int left = 0, top = 0; 554 | if ((mTrackingEdge & EDGE_LEFT) != 0) { 555 | left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth 556 | + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0; 557 | } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { 558 | left = xvel < 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? -(childWidth 559 | + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE) : 0; 560 | } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) { 561 | top = yvel < 0 || yvel == 0 && mScrollPercent > mScrollThreshold ? -(childHeight 562 | + mShadowBottom.getIntrinsicHeight() + OVERSCROLL_DISTANCE) : 0; 563 | } 564 | 565 | mDragHelper.settleCapturedViewAt(left, top); 566 | invalidate(); 567 | } 568 | 569 | @Override 570 | public int clampViewPositionHorizontal(View child, int left, int dx) { 571 | int ret = 0; 572 | if ((mTrackingEdge & EDGE_LEFT) != 0) { 573 | ret = Math.min(child.getWidth(), Math.max(left, 0)); 574 | } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { 575 | ret = Math.min(0, Math.max(left, -child.getWidth())); 576 | } 577 | return ret; 578 | } 579 | 580 | @Override 581 | public int clampViewPositionVertical(View child, int top, int dy) { 582 | int ret = 0; 583 | if ((mTrackingEdge & EDGE_BOTTOM) != 0) { 584 | ret = Math.min(0, Math.max(top, -child.getHeight())); 585 | } 586 | return ret; 587 | } 588 | 589 | @Override 590 | public void onViewDragStateChanged(int state) { 591 | super.onViewDragStateChanged(state); 592 | if (mListeners != null && !mListeners.isEmpty()) { 593 | for (SwipeListener listener : mListeners) { 594 | listener.onScrollStateChange(state, mScrollPercent); 595 | } 596 | } 597 | } 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /library/src/main/java/me/imid/swipebacklayout/lib/ViewDragHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.imid.swipebacklayout.lib; 18 | 19 | import android.content.Context; 20 | import android.support.v4.view.MotionEventCompat; 21 | import android.support.v4.view.VelocityTrackerCompat; 22 | import android.support.v4.view.ViewCompat; 23 | import android.support.v4.widget.ScrollerCompat; 24 | import android.view.MotionEvent; 25 | import android.view.VelocityTracker; 26 | import android.view.View; 27 | import android.view.ViewConfiguration; 28 | import android.view.ViewGroup; 29 | import android.view.animation.Interpolator; 30 | 31 | import java.util.Arrays; 32 | 33 | /** 34 | * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a 35 | * number of useful operations and state tracking for allowing a user to drag 36 | * and reposition views within their parent ViewGroup. 37 | */ 38 | public class ViewDragHelper { 39 | private static final String TAG = "ViewDragHelper"; 40 | 41 | /** 42 | * A null/invalid pointer ID. 43 | */ 44 | public static final int INVALID_POINTER = -1; 45 | 46 | /** 47 | * A view is not currently being dragged or animating as a result of a 48 | * fling/snap. 49 | */ 50 | public static final int STATE_IDLE = 0; 51 | 52 | /** 53 | * A view is currently being dragged. The position is currently changing as 54 | * a result of user input or simulated user input. 55 | */ 56 | public static final int STATE_DRAGGING = 1; 57 | 58 | /** 59 | * A view is currently settling into place as a result of a fling or 60 | * predefined non-interactive motion. 61 | */ 62 | public static final int STATE_SETTLING = 2; 63 | 64 | /** 65 | * Edge flag indicating that the left edge should be affected. 66 | */ 67 | public static final int EDGE_LEFT = 1 << 0; 68 | 69 | /** 70 | * Edge flag indicating that the right edge should be affected. 71 | */ 72 | public static final int EDGE_RIGHT = 1 << 1; 73 | 74 | /** 75 | * Edge flag indicating that the top edge should be affected. 76 | */ 77 | public static final int EDGE_TOP = 1 << 2; 78 | 79 | /** 80 | * Edge flag indicating that the bottom edge should be affected. 81 | */ 82 | public static final int EDGE_BOTTOM = 1 << 3; 83 | 84 | /** 85 | * Edge flag set indicating all edges should be affected. 86 | */ 87 | public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM; 88 | 89 | /** 90 | * Indicates that a check should occur along the horizontal axis 91 | */ 92 | public static final int DIRECTION_HORIZONTAL = 1 << 0; 93 | 94 | /** 95 | * Indicates that a check should occur along the vertical axis 96 | */ 97 | public static final int DIRECTION_VERTICAL = 1 << 1; 98 | 99 | /** 100 | * Indicates that a check should occur along all axes 101 | */ 102 | public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; 103 | 104 | public static final int EDGE_SIZE = 20; // dp 105 | 106 | private static final int BASE_SETTLE_DURATION = 256; // ms 107 | 108 | private static final int MAX_SETTLE_DURATION = 600; // ms 109 | 110 | // Current drag state; idle, dragging or settling 111 | private int mDragState; 112 | 113 | // Distance to travel before a drag may begin 114 | private int mTouchSlop; 115 | 116 | // Last known position/pointer tracking 117 | private int mActivePointerId = INVALID_POINTER; 118 | 119 | private float[] mInitialMotionX; 120 | 121 | private float[] mInitialMotionY; 122 | 123 | private float[] mLastMotionX; 124 | 125 | private float[] mLastMotionY; 126 | 127 | private int[] mInitialEdgeTouched; 128 | 129 | private int[] mEdgeDragsInProgress; 130 | 131 | private int[] mEdgeDragsLocked; 132 | 133 | private int mPointersDown; 134 | 135 | private VelocityTracker mVelocityTracker; 136 | 137 | private float mMaxVelocity; 138 | 139 | private float mMinVelocity; 140 | 141 | private int mEdgeSize; 142 | 143 | private int mTrackingEdges; 144 | 145 | private ScrollerCompat mScroller; 146 | 147 | private final Callback mCallback; 148 | 149 | private View mCapturedView; 150 | 151 | private boolean mReleaseInProgress; 152 | 153 | private final ViewGroup mParentView; 154 | 155 | /** 156 | * A Callback is used as a communication channel with the ViewDragHelper 157 | * back to the parent view using it. on*methods are invoked on 158 | * siginficant events and several accessor methods are expected to provide 159 | * the ViewDragHelper with more information about the state of the parent 160 | * view upon request. The callback also makes decisions governing the range 161 | * and draggability of child views. 162 | */ 163 | public static abstract class Callback { 164 | /** 165 | * Called when the drag state changes. See the STATE_* 166 | * constants for more information. 167 | * 168 | * @param state The new drag state 169 | * @see #STATE_IDLE 170 | * @see #STATE_DRAGGING 171 | * @see #STATE_SETTLING 172 | */ 173 | public void onViewDragStateChanged(int state) { 174 | } 175 | 176 | /** 177 | * Called when the captured view's position changes as the result of a 178 | * drag or settle. 179 | * 180 | * @param changedView View whose position changed 181 | * @param left New X coordinate of the left edge of the view 182 | * @param top New Y coordinate of the top edge of the view 183 | * @param dx Change in X position from the last call 184 | * @param dy Change in Y position from the last call 185 | */ 186 | public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 187 | } 188 | 189 | /** 190 | * Called when a child view is captured for dragging or settling. The ID 191 | * of the pointer currently dragging the captured view is supplied. If 192 | * activePointerId is identified as {@link #INVALID_POINTER} the capture 193 | * is programmatic instead of pointer-initiated. 194 | * 195 | * @param capturedChild Child view that was captured 196 | * @param activePointerId Pointer id tracking the child capture 197 | */ 198 | public void onViewCaptured(View capturedChild, int activePointerId) { 199 | } 200 | 201 | /** 202 | * Called when the child view is no longer being actively dragged. The 203 | * fling velocity is also supplied, if relevant. The velocity values may 204 | * be clamped to system minimums or maximums. 205 | *

206 | * Calling code may decide to fling or otherwise release the view to let 207 | * it settle into place. It should do so using 208 | * {@link #settleCapturedViewAt(int, int)} or 209 | * {@link #flingCapturedView(int, int, int, int)}. If the Callback 210 | * invokes one of these methods, the ViewDragHelper will enter 211 | * {@link #STATE_SETTLING} and the view capture will not fully end until 212 | * it comes to a complete stop. If neither of these methods is invoked 213 | * before onViewReleased returns, the view will stop in 214 | * place and the ViewDragHelper will return to {@link #STATE_IDLE}. 215 | *

216 | * 217 | * @param releasedChild The captured child view now being released 218 | * @param xvel X velocity of the pointer as it left the screen in pixels 219 | * per second. 220 | * @param yvel Y velocity of the pointer as it left the screen in pixels 221 | * per second. 222 | */ 223 | public void onViewReleased(View releasedChild, float xvel, float yvel) { 224 | } 225 | 226 | /** 227 | * Called when one of the subscribed edges in the parent view has been 228 | * touched by the user while no child view is currently captured. 229 | * 230 | * @param edgeFlags A combination of edge flags describing the edge(s) 231 | * currently touched 232 | * @param pointerId ID of the pointer touching the described edge(s) 233 | * @see #EDGE_LEFT 234 | * @see #EDGE_TOP 235 | * @see #EDGE_RIGHT 236 | * @see #EDGE_BOTTOM 237 | */ 238 | public void onEdgeTouched(int edgeFlags, int pointerId) { 239 | } 240 | 241 | /** 242 | * Called when the given edge may become locked. This can happen if an 243 | * edge drag was preliminarily rejected before beginning, but after 244 | * {@link #onEdgeTouched(int, int)} was called. This method should 245 | * return true to lock this edge or false to leave it unlocked. The 246 | * default behavior is to leave edges unlocked. 247 | * 248 | * @param edgeFlags A combination of edge flags describing the edge(s) 249 | * locked 250 | * @return true to lock the edge, false to leave it unlocked 251 | */ 252 | public boolean onEdgeLock(int edgeFlags) { 253 | return false; 254 | } 255 | 256 | /** 257 | * Called when the user has started a deliberate drag away from one of 258 | * the subscribed edges in the parent view while no child view is 259 | * currently captured. 260 | * 261 | * @param edgeFlags A combination of edge flags describing the edge(s) 262 | * dragged 263 | * @param pointerId ID of the pointer touching the described edge(s) 264 | * @see #EDGE_LEFT 265 | * @see #EDGE_TOP 266 | * @see #EDGE_RIGHT 267 | * @see #EDGE_BOTTOM 268 | */ 269 | public void onEdgeDragStarted(int edgeFlags, int pointerId) { 270 | } 271 | 272 | /** 273 | * Called to determine the Z-order of child views. 274 | * 275 | * @param index the ordered position to query for 276 | * @return index of the view that should be ordered at position 277 | * index 278 | */ 279 | public int getOrderedChildIndex(int index) { 280 | return index; 281 | } 282 | 283 | /** 284 | * Return the magnitude of a draggable child view's horizontal range of 285 | * motion in pixels. This method should return 0 for views that cannot 286 | * move horizontally. 287 | * 288 | * @param child Child view to check 289 | * @return range of horizontal motion in pixels 290 | */ 291 | public int getViewHorizontalDragRange(View child) { 292 | return 0; 293 | } 294 | 295 | /** 296 | * Return the magnitude of a draggable child view's vertical range of 297 | * motion in pixels. This method should return 0 for views that cannot 298 | * move vertically. 299 | * 300 | * @param child Child view to check 301 | * @return range of vertical motion in pixels 302 | */ 303 | public int getViewVerticalDragRange(View child) { 304 | return 0; 305 | } 306 | 307 | /** 308 | * Called when the user's input indicates that they want to capture the 309 | * given child view with the pointer indicated by pointerId. The 310 | * callback should return true if the user is permitted to drag the 311 | * given view with the indicated pointer. 312 | *

313 | * ViewDragHelper may call this method multiple times for the same view 314 | * even if the view is already captured; this indicates that a new 315 | * pointer is trying to take control of the view. 316 | *

317 | *

318 | * If this method returns true, a call to 319 | * {@link #onViewCaptured(android.view.View, int)} will follow if the 320 | * capture is successful. 321 | *

322 | * 323 | * @param child Child the user is attempting to capture 324 | * @param pointerId ID of the pointer attempting the capture 325 | * @return true if capture should be allowed, false otherwise 326 | */ 327 | public abstract boolean tryCaptureView(View child, int pointerId); 328 | 329 | /** 330 | * Restrict the motion of the dragged child view along the horizontal 331 | * axis. The default implementation does not allow horizontal motion; 332 | * the extending class must override this method and provide the desired 333 | * clamping. 334 | * 335 | * @param child Child view being dragged 336 | * @param left Attempted motion along the X axis 337 | * @param dx Proposed change in position for left 338 | * @return The new clamped position for left 339 | */ 340 | public int clampViewPositionHorizontal(View child, int left, int dx) { 341 | return 0; 342 | } 343 | 344 | /** 345 | * Restrict the motion of the dragged child view along the vertical 346 | * axis. The default implementation does not allow vertical motion; the 347 | * extending class must override this method and provide the desired 348 | * clamping. 349 | * 350 | * @param child Child view being dragged 351 | * @param top Attempted motion along the Y axis 352 | * @param dy Proposed change in position for top 353 | * @return The new clamped position for top 354 | */ 355 | public int clampViewPositionVertical(View child, int top, int dy) { 356 | return 0; 357 | } 358 | } 359 | 360 | /** 361 | * Interpolator defining the animation curve for mScroller 362 | */ 363 | private static final Interpolator sInterpolator = new Interpolator() { 364 | public float getInterpolation(float t) { 365 | t -= 1.0f; 366 | return t * t * t * t * t + 1.0f; 367 | } 368 | }; 369 | 370 | private final Runnable mSetIdleRunnable = new Runnable() { 371 | public void run() { 372 | setDragState(STATE_IDLE); 373 | } 374 | }; 375 | 376 | /** 377 | * Factory method to create a new ViewDragHelper. 378 | * 379 | * @param forParent Parent view to monitor 380 | * @param cb Callback to provide information and receive events 381 | * @return a new ViewDragHelper instance 382 | */ 383 | public static ViewDragHelper create(ViewGroup forParent, Callback cb) { 384 | return new ViewDragHelper(forParent.getContext(), forParent, cb); 385 | } 386 | 387 | /** 388 | * Factory method to create a new ViewDragHelper. 389 | * 390 | * @param forParent Parent view to monitor 391 | * @param sensitivity Multiplier for how sensitive the helper should be 392 | * about detecting the start of a drag. Larger values are more 393 | * sensitive. 1.0f is normal. 394 | * @param cb Callback to provide information and receive events 395 | * @return a new ViewDragHelper instance 396 | */ 397 | public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) { 398 | final ViewDragHelper helper = create(forParent, cb); 399 | helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); 400 | return helper; 401 | } 402 | 403 | /** 404 | * Apps should use ViewDragHelper.create() to get a new instance. This will 405 | * allow VDH to use internal compatibility implementations for different 406 | * platform versions. 407 | * 408 | * @param context Context to initialize config-dependent params from 409 | * @param forParent Parent view to monitor 410 | */ 411 | private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) { 412 | if (forParent == null) { 413 | throw new IllegalArgumentException("Parent view may not be null"); 414 | } 415 | if (cb == null) { 416 | throw new IllegalArgumentException("Callback may not be null"); 417 | } 418 | 419 | mParentView = forParent; 420 | mCallback = cb; 421 | 422 | final ViewConfiguration vc = ViewConfiguration.get(context); 423 | final float density = context.getResources().getDisplayMetrics().density; 424 | mEdgeSize = (int) (EDGE_SIZE * density + 0.5f); 425 | 426 | mTouchSlop = vc.getScaledTouchSlop(); 427 | mMaxVelocity = vc.getScaledMaximumFlingVelocity(); 428 | mMinVelocity = vc.getScaledMinimumFlingVelocity(); 429 | mScroller = ScrollerCompat.create(context, sInterpolator); 430 | } 431 | 432 | /** 433 | * Sets the sensitivity of the dragger. 434 | * 435 | * @param context The application context. 436 | * @param sensitivity value between 0 and 1, the final value for touchSlop = 437 | * ViewConfiguration.getScaledTouchSlop * (1 / s); 438 | */ 439 | public void setSensitivity(Context context, float sensitivity) { 440 | float s = Math.max(0f, Math.min(1.0f, sensitivity)); 441 | ViewConfiguration viewConfiguration = ViewConfiguration.get(context); 442 | mTouchSlop = (int) (viewConfiguration.getScaledTouchSlop() * (1 / s)); 443 | } 444 | 445 | /** 446 | * Set the minimum velocity that will be detected as having a magnitude 447 | * greater than zero in pixels per second. Callback methods accepting a 448 | * velocity will be clamped appropriately. 449 | * 450 | * @param minVel minimum velocity to detect 451 | */ 452 | public void setMinVelocity(float minVel) { 453 | mMinVelocity = minVel; 454 | } 455 | 456 | /** 457 | * Set the max velocity that will be detected as having a magnitude 458 | * greater than zero in pixels per second. Callback methods accepting a 459 | * velocity will be clamped appropriately. 460 | * 461 | * @param maxVel max velocity to detect 462 | */ 463 | public void setMaxVelocity(float maxVel) { 464 | mMaxVelocity = maxVel; 465 | } 466 | 467 | /** 468 | * Return the currently configured minimum velocity. Any flings with a 469 | * magnitude less than this value in pixels per second. Callback methods 470 | * accepting a velocity will receive zero as a velocity value if the real 471 | * detected velocity was below this threshold. 472 | * 473 | * @return the minimum velocity that will be detected 474 | */ 475 | public float getMinVelocity() { 476 | return mMinVelocity; 477 | } 478 | 479 | /** 480 | * Retrieve the current drag state of this helper. This will return one of 481 | * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. 482 | * 483 | * @return The current drag state 484 | */ 485 | public int getViewDragState() { 486 | return mDragState; 487 | } 488 | 489 | /** 490 | * Enable edge tracking for the selected edges of the parent view. The 491 | * callback's 492 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeTouched(int, int)} 493 | * and 494 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeDragStarted(int, int)} 495 | * methods will only be invoked for edges for which edge tracking has been 496 | * enabled. 497 | * 498 | * @param edgeFlags Combination of edge flags describing the edges to watch 499 | * @see #EDGE_LEFT 500 | * @see #EDGE_TOP 501 | * @see #EDGE_RIGHT 502 | * @see #EDGE_BOTTOM 503 | */ 504 | public void setEdgeTrackingEnabled(int edgeFlags) { 505 | mTrackingEdges = edgeFlags; 506 | } 507 | 508 | /** 509 | * Return the size of an edge. This is the range in pixels along the edges 510 | * of this view that will actively detect edge touches or drags if edge 511 | * tracking is enabled. 512 | * 513 | * @return The size of an edge in pixels 514 | * @see #setEdgeTrackingEnabled(int) 515 | */ 516 | public int getEdgeSize() { 517 | return mEdgeSize; 518 | } 519 | 520 | /** 521 | * Set the size of an edge. This is the range in pixels along the edges of 522 | * this view that will actively detect edge touches or drags if edge 523 | * tracking is enabled. 524 | * 525 | * @param size The size of an edge in pixels 526 | */ 527 | public void setEdgeSize(int size) { 528 | mEdgeSize = size; 529 | } 530 | 531 | /** 532 | * Capture a specific child view for dragging within the parent. The 533 | * callback will be notified but 534 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#tryCaptureView(android.view.View, int)} 535 | * will not be asked permission to capture this view. 536 | * 537 | * @param childView Child view to capture 538 | * @param activePointerId ID of the pointer that is dragging the captured 539 | * child view 540 | */ 541 | public void captureChildView(View childView, int activePointerId) { 542 | if (childView.getParent() != mParentView) { 543 | throw new IllegalArgumentException("captureChildView: parameter must be a descendant " 544 | + "of the ViewDragHelper's tracked parent view (" + mParentView + ")"); 545 | } 546 | 547 | mCapturedView = childView; 548 | mActivePointerId = activePointerId; 549 | mCallback.onViewCaptured(childView, activePointerId); 550 | setDragState(STATE_DRAGGING); 551 | } 552 | 553 | /** 554 | * @return The currently captured view, or null if no view has been 555 | * captured. 556 | */ 557 | public View getCapturedView() { 558 | return mCapturedView; 559 | } 560 | 561 | /** 562 | * @return The ID of the pointer currently dragging the captured view, or 563 | * {@link #INVALID_POINTER}. 564 | */ 565 | public int getActivePointerId() { 566 | return mActivePointerId; 567 | } 568 | 569 | /** 570 | * @return The minimum distance in pixels that the user must travel to 571 | * initiate a drag 572 | */ 573 | public int getTouchSlop() { 574 | return mTouchSlop; 575 | } 576 | 577 | /** 578 | * The result of a call to this method is equivalent to 579 | * {@link #processTouchEvent(android.view.MotionEvent)} receiving an 580 | * ACTION_CANCEL event. 581 | */ 582 | public void cancel() { 583 | mActivePointerId = INVALID_POINTER; 584 | clearMotionHistory(); 585 | 586 | if (mVelocityTracker != null) { 587 | mVelocityTracker.recycle(); 588 | mVelocityTracker = null; 589 | } 590 | } 591 | 592 | /** 593 | * {@link #cancel()}, but also abort all motion in progress and snap to the 594 | * end of any animation. 595 | */ 596 | public void abort() { 597 | cancel(); 598 | if (mDragState == STATE_SETTLING) { 599 | final int oldX = mScroller.getCurrX(); 600 | final int oldY = mScroller.getCurrY(); 601 | mScroller.abortAnimation(); 602 | final int newX = mScroller.getCurrX(); 603 | final int newY = mScroller.getCurrY(); 604 | mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY); 605 | } 606 | setDragState(STATE_IDLE); 607 | } 608 | 609 | /** 610 | * Animate the view child to the given (left, top) position. If 611 | * this method returns true, the caller should invoke 612 | * {@link #continueSettling(boolean)} on each subsequent frame to continue 613 | * the motion until it returns false. If this method returns false there is 614 | * no further work to do to complete the movement. 615 | *

616 | * This operation does not count as a capture event, though 617 | * {@link #getCapturedView()} will still report the sliding view while the 618 | * slide is in progress. 619 | *

620 | * 621 | * @param child Child view to capture and animate 622 | * @param finalLeft Final left position of child 623 | * @param finalTop Final top position of child 624 | * @return true if animation should continue through 625 | * {@link #continueSettling(boolean)} calls 626 | */ 627 | public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) { 628 | mCapturedView = child; 629 | mActivePointerId = INVALID_POINTER; 630 | 631 | return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0); 632 | } 633 | 634 | /** 635 | * Settle the captured view at the given (left, top) position. The 636 | * appropriate velocity from prior motion will be taken into account. If 637 | * this method returns true, the caller should invoke 638 | * {@link #continueSettling(boolean)} on each subsequent frame to continue 639 | * the motion until it returns false. If this method returns false there is 640 | * no further work to do to complete the movement. 641 | * 642 | * @param finalLeft Settled left edge position for the captured view 643 | * @param finalTop Settled top edge position for the captured view 644 | * @return true if animation should continue through 645 | * {@link #continueSettling(boolean)} calls 646 | */ 647 | public boolean settleCapturedViewAt(int finalLeft, int finalTop) { 648 | if (!mReleaseInProgress) { 649 | throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " 650 | + "Callback#onViewReleased"); 651 | } 652 | 653 | return forceSettleCapturedViewAt(finalLeft, finalTop, 654 | (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), 655 | (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId)); 656 | } 657 | 658 | /** 659 | * Settle the captured view at the given (left, top) position. 660 | * 661 | * @param finalLeft Target left position for the captured view 662 | * @param finalTop Target top position for the captured view 663 | * @param xvel Horizontal velocity 664 | * @param yvel Vertical velocity 665 | * @return true if animation should continue through 666 | * {@link #continueSettling(boolean)} calls 667 | */ 668 | private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) { 669 | final int startLeft = mCapturedView.getLeft(); 670 | final int startTop = mCapturedView.getTop(); 671 | final int dx = finalLeft - startLeft; 672 | final int dy = finalTop - startTop; 673 | 674 | if (dx == 0 && dy == 0) { 675 | // Nothing to do. Send callbacks, be done. 676 | mScroller.abortAnimation(); 677 | setDragState(STATE_IDLE); 678 | return false; 679 | } 680 | 681 | final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel); 682 | mScroller.startScroll(startLeft, startTop, dx, dy, duration); 683 | 684 | setDragState(STATE_SETTLING); 685 | return true; 686 | } 687 | 688 | private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) { 689 | xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity); 690 | yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity); 691 | final int absDx = Math.abs(dx); 692 | final int absDy = Math.abs(dy); 693 | final int absXVel = Math.abs(xvel); 694 | final int absYVel = Math.abs(yvel); 695 | final int addedVel = absXVel + absYVel; 696 | final int addedDistance = absDx + absDy; 697 | 698 | final float xweight = xvel != 0 ? (float) absXVel / addedVel : (float) absDx 699 | / addedDistance; 700 | final float yweight = yvel != 0 ? (float) absYVel / addedVel : (float) absDy 701 | / addedDistance; 702 | 703 | int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child)); 704 | int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child)); 705 | 706 | return (int) (xduration * xweight + yduration * yweight); 707 | } 708 | 709 | private int computeAxisDuration(int delta, int velocity, int motionRange) { 710 | if (delta == 0) { 711 | return 0; 712 | } 713 | 714 | final int width = mParentView.getWidth(); 715 | final int halfWidth = width / 2; 716 | final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width); 717 | final float distance = halfWidth + halfWidth 718 | * distanceInfluenceForSnapDuration(distanceRatio); 719 | 720 | int duration; 721 | velocity = Math.abs(velocity); 722 | if (velocity > 0) { 723 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 724 | } else { 725 | final float range = (float) Math.abs(delta) / motionRange; 726 | duration = (int) ((range + 1) * BASE_SETTLE_DURATION); 727 | } 728 | return Math.min(duration, MAX_SETTLE_DURATION); 729 | } 730 | 731 | /** 732 | * Clamp the magnitude of value for absMin and absMax. If the value is below 733 | * the minimum, it will be clamped to zero. If the value is above the 734 | * maximum, it will be clamped to the maximum. 735 | * 736 | * @param value Value to clamp 737 | * @param absMin Absolute value of the minimum significant value to return 738 | * @param absMax Absolute value of the maximum value to return 739 | * @return The clamped value with the same sign as value 740 | */ 741 | private int clampMag(int value, int absMin, int absMax) { 742 | final int absValue = Math.abs(value); 743 | if (absValue < absMin) 744 | return 0; 745 | if (absValue > absMax) 746 | return value > 0 ? absMax : -absMax; 747 | return value; 748 | } 749 | 750 | /** 751 | * Clamp the magnitude of value for absMin and absMax. If the value is below 752 | * the minimum, it will be clamped to zero. If the value is above the 753 | * maximum, it will be clamped to the maximum. 754 | * 755 | * @param value Value to clamp 756 | * @param absMin Absolute value of the minimum significant value to return 757 | * @param absMax Absolute value of the maximum value to return 758 | * @return The clamped value with the same sign as value 759 | */ 760 | private float clampMag(float value, float absMin, float absMax) { 761 | final float absValue = Math.abs(value); 762 | if (absValue < absMin) 763 | return 0; 764 | if (absValue > absMax) 765 | return value > 0 ? absMax : -absMax; 766 | return value; 767 | } 768 | 769 | private float distanceInfluenceForSnapDuration(float f) { 770 | f -= 0.5f; // center the values about 0. 771 | f *= 0.3f * Math.PI / 2.0f; 772 | return (float) Math.sin(f); 773 | } 774 | 775 | /** 776 | * Settle the captured view based on standard free-moving fling behavior. 777 | * The caller should invoke {@link #continueSettling(boolean)} on each 778 | * subsequent frame to continue the motion until it returns false. 779 | * 780 | * @param minLeft Minimum X position for the view's left edge 781 | * @param minTop Minimum Y position for the view's top edge 782 | * @param maxLeft Maximum X position for the view's left edge 783 | * @param maxTop Maximum Y position for the view's top edge 784 | */ 785 | public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) { 786 | if (!mReleaseInProgress) { 787 | throw new IllegalStateException("Cannot flingCapturedView outside of a call to " 788 | + "Callback#onViewReleased"); 789 | } 790 | 791 | mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(), 792 | (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), 793 | (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId), 794 | minLeft, maxLeft, minTop, maxTop); 795 | 796 | setDragState(STATE_SETTLING); 797 | } 798 | 799 | /** 800 | * Move the captured settling view by the appropriate amount for the current 801 | * time. If continueSettling returns true, the caller should 802 | * call it again on the next frame to continue. 803 | * 804 | * @param deferCallbacks true if state callbacks should be deferred via 805 | * posted message. Set this to true if you are calling this 806 | * method from {@link android.view.View#computeScroll()} or 807 | * similar methods invoked as part of layout or drawing. 808 | * @return true if settle is still in progress 809 | */ 810 | public boolean continueSettling(boolean deferCallbacks) { 811 | if (mDragState == STATE_SETTLING) { 812 | boolean keepGoing = mScroller.computeScrollOffset(); 813 | final int x = mScroller.getCurrX(); 814 | final int y = mScroller.getCurrY(); 815 | final int dx = x - mCapturedView.getLeft(); 816 | final int dy = y - mCapturedView.getTop(); 817 | 818 | if (dx != 0) { 819 | mCapturedView.offsetLeftAndRight(dx); 820 | } 821 | if (dy != 0) { 822 | mCapturedView.offsetTopAndBottom(dy); 823 | } 824 | 825 | if (dx != 0 || dy != 0) { 826 | mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy); 827 | } 828 | 829 | if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) { 830 | // Close enough. The interpolator/scroller might think we're 831 | // still moving 832 | // but the user sure doesn't. 833 | mScroller.abortAnimation(); 834 | keepGoing = mScroller.isFinished(); 835 | } 836 | 837 | if (!keepGoing) { 838 | if (deferCallbacks) { 839 | mParentView.post(mSetIdleRunnable); 840 | } else { 841 | setDragState(STATE_IDLE); 842 | } 843 | } 844 | } 845 | 846 | return mDragState == STATE_SETTLING; 847 | } 848 | 849 | /** 850 | * Like all callback events this must happen on the UI thread, but release 851 | * involves some extra semantics. During a release (mReleaseInProgress) is 852 | * the only time it is valid to call {@link #settleCapturedViewAt(int, int)} 853 | * or {@link #flingCapturedView(int, int, int, int)}. 854 | */ 855 | private void dispatchViewReleased(float xvel, float yvel) { 856 | mReleaseInProgress = true; 857 | mCallback.onViewReleased(mCapturedView, xvel, yvel); 858 | mReleaseInProgress = false; 859 | 860 | if (mDragState == STATE_DRAGGING) { 861 | // onViewReleased didn't call a method that would have changed this. 862 | // Go idle. 863 | setDragState(STATE_IDLE); 864 | } 865 | } 866 | 867 | private void clearMotionHistory() { 868 | if (mInitialMotionX == null) { 869 | return; 870 | } 871 | Arrays.fill(mInitialMotionX, 0); 872 | Arrays.fill(mInitialMotionY, 0); 873 | Arrays.fill(mLastMotionX, 0); 874 | Arrays.fill(mLastMotionY, 0); 875 | Arrays.fill(mInitialEdgeTouched, 0); 876 | Arrays.fill(mEdgeDragsInProgress, 0); 877 | Arrays.fill(mEdgeDragsLocked, 0); 878 | mPointersDown = 0; 879 | } 880 | 881 | private void clearMotionHistory(int pointerId) { 882 | if (mInitialMotionX == null) { 883 | return; 884 | } 885 | mInitialMotionX[pointerId] = 0; 886 | mInitialMotionY[pointerId] = 0; 887 | mLastMotionX[pointerId] = 0; 888 | mLastMotionY[pointerId] = 0; 889 | mInitialEdgeTouched[pointerId] = 0; 890 | mEdgeDragsInProgress[pointerId] = 0; 891 | mEdgeDragsLocked[pointerId] = 0; 892 | mPointersDown &= ~(1 << pointerId); 893 | } 894 | 895 | private void ensureMotionHistorySizeForId(int pointerId) { 896 | if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) { 897 | float[] imx = new float[pointerId + 1]; 898 | float[] imy = new float[pointerId + 1]; 899 | float[] lmx = new float[pointerId + 1]; 900 | float[] lmy = new float[pointerId + 1]; 901 | int[] iit = new int[pointerId + 1]; 902 | int[] edip = new int[pointerId + 1]; 903 | int[] edl = new int[pointerId + 1]; 904 | 905 | if (mInitialMotionX != null) { 906 | System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length); 907 | System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length); 908 | System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length); 909 | System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length); 910 | System.arraycopy(mInitialEdgeTouched, 0, iit, 0, mInitialEdgeTouched.length); 911 | System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length); 912 | System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length); 913 | } 914 | 915 | mInitialMotionX = imx; 916 | mInitialMotionY = imy; 917 | mLastMotionX = lmx; 918 | mLastMotionY = lmy; 919 | mInitialEdgeTouched = iit; 920 | mEdgeDragsInProgress = edip; 921 | mEdgeDragsLocked = edl; 922 | } 923 | } 924 | 925 | private void saveInitialMotion(float x, float y, int pointerId) { 926 | ensureMotionHistorySizeForId(pointerId); 927 | mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x; 928 | mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y; 929 | mInitialEdgeTouched[pointerId] = getEdgeTouched((int) x, (int) y); 930 | mPointersDown |= 1 << pointerId; 931 | } 932 | 933 | private void saveLastMotion(MotionEvent ev) { 934 | final int pointerCount = MotionEventCompat.getPointerCount(ev); 935 | for (int i = 0; i < pointerCount; i++) { 936 | final int pointerId = MotionEventCompat.getPointerId(ev, i); 937 | final float x = MotionEventCompat.getX(ev, i); 938 | final float y = MotionEventCompat.getY(ev, i); 939 | mLastMotionX[pointerId] = x; 940 | mLastMotionY[pointerId] = y; 941 | } 942 | } 943 | 944 | /** 945 | * Check if the given pointer ID represents a pointer that is currently down 946 | * (to the best of the ViewDragHelper's knowledge). 947 | *

948 | * The state used to report this information is populated by the methods 949 | * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or 950 | * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these 951 | * methods has not been called for all relevant MotionEvents to track, the 952 | * information reported by this method may be stale or incorrect. 953 | *

954 | * 955 | * @param pointerId pointer ID to check; corresponds to IDs provided by 956 | * MotionEvent 957 | * @return true if the pointer with the given ID is still down 958 | */ 959 | public boolean isPointerDown(int pointerId) { 960 | return (mPointersDown & 1 << pointerId) != 0; 961 | } 962 | 963 | void setDragState(int state) { 964 | if (mDragState != state) { 965 | mDragState = state; 966 | mCallback.onViewDragStateChanged(state); 967 | if (state == STATE_IDLE) { 968 | mCapturedView = null; 969 | } 970 | } 971 | } 972 | 973 | /** 974 | * Attempt to capture the view with the given pointer ID. The callback will 975 | * be involved. This will put us into the "dragging" state. If we've already 976 | * captured this view with this pointer this method will immediately return 977 | * true without consulting the callback. 978 | * 979 | * @param toCapture View to capture 980 | * @param pointerId Pointer to capture with 981 | * @return true if capture was successful 982 | */ 983 | boolean tryCaptureViewForDrag(View toCapture, int pointerId) { 984 | if (toCapture == mCapturedView && mActivePointerId == pointerId) { 985 | // Already done! 986 | return true; 987 | } 988 | if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) { 989 | mActivePointerId = pointerId; 990 | captureChildView(toCapture, pointerId); 991 | return true; 992 | } 993 | return false; 994 | } 995 | 996 | /** 997 | * Tests scrollability within child views of v given a delta of dx. 998 | * 999 | * @param v View to test for horizontal scrollability 1000 | * @param checkV Whether the view v passed should itself be checked for 1001 | * scrollability (true), or just its children (false). 1002 | * @param dx Delta scrolled in pixels along the X axis 1003 | * @param dy Delta scrolled in pixels along the Y axis 1004 | * @param x X coordinate of the active touch point 1005 | * @param y Y coordinate of the active touch point 1006 | * @return true if child views of v can be scrolled by delta of dx. 1007 | */ 1008 | protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) { 1009 | if (v instanceof ViewGroup) { 1010 | final ViewGroup group = (ViewGroup) v; 1011 | final int scrollX = v.getScrollX(); 1012 | final int scrollY = v.getScrollY(); 1013 | final int count = group.getChildCount(); 1014 | // Count backwards - let topmost views consume scroll distance 1015 | // first. 1016 | for (int i = count - 1; i >= 0; i--) { 1017 | // TODO: Add versioned support here for transformed views. 1018 | // This will not work for transformed views in Honeycomb+ 1019 | final View child = group.getChildAt(i); 1020 | if (x + scrollX >= child.getLeft() 1021 | && x + scrollX < child.getRight() 1022 | && y + scrollY >= child.getTop() 1023 | && y + scrollY < child.getBottom() 1024 | && canScroll(child, true, dx, dy, x + scrollX - child.getLeft(), y 1025 | + scrollY - child.getTop())) { 1026 | return true; 1027 | } 1028 | } 1029 | } 1030 | 1031 | return checkV 1032 | && (ViewCompat.canScrollHorizontally(v, -dx) || ViewCompat.canScrollVertically(v, 1033 | -dy)); 1034 | } 1035 | 1036 | /** 1037 | * Check if this event as provided to the parent view's 1038 | * onInterceptTouchEvent should cause the parent to intercept the touch 1039 | * event stream. 1040 | * 1041 | * @param ev MotionEvent provided to onInterceptTouchEvent 1042 | * @return true if the parent view should return true from 1043 | * onInterceptTouchEvent 1044 | */ 1045 | public boolean shouldInterceptTouchEvent(MotionEvent ev) { 1046 | final int action = MotionEventCompat.getActionMasked(ev); 1047 | final int actionIndex = MotionEventCompat.getActionIndex(ev); 1048 | 1049 | if (action == MotionEvent.ACTION_DOWN) { 1050 | // Reset things for a new event stream, just in case we didn't get 1051 | // the whole previous stream. 1052 | cancel(); 1053 | } 1054 | 1055 | if (mVelocityTracker == null) { 1056 | mVelocityTracker = VelocityTracker.obtain(); 1057 | } 1058 | mVelocityTracker.addMovement(ev); 1059 | 1060 | switch (action) { 1061 | case MotionEvent.ACTION_DOWN: { 1062 | final float x = ev.getX(); 1063 | final float y = ev.getY(); 1064 | final int pointerId = MotionEventCompat.getPointerId(ev, 0); 1065 | saveInitialMotion(x, y, pointerId); 1066 | 1067 | final View toCapture = findTopChildUnder((int) x, (int) y); 1068 | 1069 | // Catch a settling view if possible. 1070 | if (toCapture == mCapturedView && mDragState == STATE_SETTLING) { 1071 | tryCaptureViewForDrag(toCapture, pointerId); 1072 | } 1073 | 1074 | final int edgesTouched = mInitialEdgeTouched[pointerId]; 1075 | if ((edgesTouched & mTrackingEdges) != 0) { 1076 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); 1077 | } 1078 | break; 1079 | } 1080 | 1081 | case MotionEventCompat.ACTION_POINTER_DOWN: { 1082 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); 1083 | final float x = MotionEventCompat.getX(ev, actionIndex); 1084 | final float y = MotionEventCompat.getY(ev, actionIndex); 1085 | 1086 | saveInitialMotion(x, y, pointerId); 1087 | 1088 | // A ViewDragHelper can only manipulate one view at a time. 1089 | if (mDragState == STATE_IDLE) { 1090 | final int edgesTouched = mInitialEdgeTouched[pointerId]; 1091 | if ((edgesTouched & mTrackingEdges) != 0) { 1092 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); 1093 | } 1094 | } else if (mDragState == STATE_SETTLING) { 1095 | // Catch a settling view if possible. 1096 | final View toCapture = findTopChildUnder((int) x, (int) y); 1097 | if (toCapture == mCapturedView) { 1098 | tryCaptureViewForDrag(toCapture, pointerId); 1099 | } 1100 | } 1101 | break; 1102 | } 1103 | 1104 | case MotionEvent.ACTION_MOVE: { 1105 | // First to cross a touch slop over a draggable view wins. Also 1106 | // report edge drags. 1107 | final int pointerCount = MotionEventCompat.getPointerCount(ev); 1108 | for (int i = 0; i < pointerCount; i++) { 1109 | final int pointerId = MotionEventCompat.getPointerId(ev, i); 1110 | final float x = MotionEventCompat.getX(ev, i); 1111 | final float y = MotionEventCompat.getY(ev, i); 1112 | final float dx = x - mInitialMotionX[pointerId]; 1113 | final float dy = y - mInitialMotionY[pointerId]; 1114 | 1115 | reportNewEdgeDrags(dx, dy, pointerId); 1116 | if (mDragState == STATE_DRAGGING) { 1117 | // Callback might have started an edge drag 1118 | break; 1119 | } 1120 | 1121 | final View toCapture = findTopChildUnder((int) x, (int) y); 1122 | if (toCapture != null && checkTouchSlop(toCapture, dx, dy) 1123 | && tryCaptureViewForDrag(toCapture, pointerId)) { 1124 | break; 1125 | } 1126 | } 1127 | saveLastMotion(ev); 1128 | break; 1129 | } 1130 | 1131 | case MotionEventCompat.ACTION_POINTER_UP: { 1132 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); 1133 | clearMotionHistory(pointerId); 1134 | break; 1135 | } 1136 | 1137 | case MotionEvent.ACTION_UP: 1138 | case MotionEvent.ACTION_CANCEL: { 1139 | cancel(); 1140 | break; 1141 | } 1142 | } 1143 | 1144 | return mDragState == STATE_DRAGGING; 1145 | } 1146 | 1147 | /** 1148 | * Process a touch event received by the parent view. This method will 1149 | * dispatch callback events as needed before returning. The parent view's 1150 | * onTouchEvent implementation should call this. 1151 | * 1152 | * @param ev The touch event received by the parent view 1153 | */ 1154 | public void processTouchEvent(MotionEvent ev) { 1155 | final int action = MotionEventCompat.getActionMasked(ev); 1156 | final int actionIndex = MotionEventCompat.getActionIndex(ev); 1157 | 1158 | if (action == MotionEvent.ACTION_DOWN) { 1159 | // Reset things for a new event stream, just in case we didn't get 1160 | // the whole previous stream. 1161 | cancel(); 1162 | } 1163 | 1164 | if (mVelocityTracker == null) { 1165 | mVelocityTracker = VelocityTracker.obtain(); 1166 | } 1167 | mVelocityTracker.addMovement(ev); 1168 | 1169 | switch (action) { 1170 | case MotionEvent.ACTION_DOWN: { 1171 | final float x = ev.getX(); 1172 | final float y = ev.getY(); 1173 | final int pointerId = MotionEventCompat.getPointerId(ev, 0); 1174 | final View toCapture = findTopChildUnder((int) x, (int) y); 1175 | 1176 | saveInitialMotion(x, y, pointerId); 1177 | 1178 | // Since the parent is already directly processing this touch 1179 | // event, 1180 | // there is no reason to delay for a slop before dragging. 1181 | // Start immediately if possible. 1182 | tryCaptureViewForDrag(toCapture, pointerId); 1183 | 1184 | final int edgesTouched = mInitialEdgeTouched[pointerId]; 1185 | if ((edgesTouched & mTrackingEdges) != 0) { 1186 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); 1187 | } 1188 | break; 1189 | } 1190 | 1191 | case MotionEventCompat.ACTION_POINTER_DOWN: { 1192 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); 1193 | final float x = MotionEventCompat.getX(ev, actionIndex); 1194 | final float y = MotionEventCompat.getY(ev, actionIndex); 1195 | 1196 | saveInitialMotion(x, y, pointerId); 1197 | 1198 | // A ViewDragHelper can only manipulate one view at a time. 1199 | if (mDragState == STATE_IDLE) { 1200 | // If we're idle we can do anything! Treat it like a normal 1201 | // down event. 1202 | 1203 | final View toCapture = findTopChildUnder((int) x, (int) y); 1204 | tryCaptureViewForDrag(toCapture, pointerId); 1205 | 1206 | final int edgesTouched = mInitialEdgeTouched[pointerId]; 1207 | if ((edgesTouched & mTrackingEdges) != 0) { 1208 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); 1209 | } 1210 | } else if (isCapturedViewUnder((int) x, (int) y)) { 1211 | // We're still tracking a captured view. If the same view is 1212 | // under this 1213 | // point, we'll swap to controlling it with this pointer 1214 | // instead. 1215 | // (This will still work if we're "catching" a settling 1216 | // view.) 1217 | 1218 | tryCaptureViewForDrag(mCapturedView, pointerId); 1219 | } 1220 | break; 1221 | } 1222 | 1223 | case MotionEvent.ACTION_MOVE: { 1224 | if (mDragState == STATE_DRAGGING) { 1225 | final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1226 | final float x = MotionEventCompat.getX(ev, index); 1227 | final float y = MotionEventCompat.getY(ev, index); 1228 | final int idx = (int) (x - mLastMotionX[mActivePointerId]); 1229 | final int idy = (int) (y - mLastMotionY[mActivePointerId]); 1230 | 1231 | dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy); 1232 | 1233 | saveLastMotion(ev); 1234 | } else { 1235 | // Check to see if any pointer is now over a draggable view. 1236 | final int pointerCount = MotionEventCompat.getPointerCount(ev); 1237 | for (int i = 0; i < pointerCount; i++) { 1238 | final int pointerId = MotionEventCompat.getPointerId(ev, i); 1239 | final float x = MotionEventCompat.getX(ev, i); 1240 | final float y = MotionEventCompat.getY(ev, i); 1241 | final float dx = x - mInitialMotionX[pointerId]; 1242 | final float dy = y - mInitialMotionY[pointerId]; 1243 | 1244 | reportNewEdgeDrags(dx, dy, pointerId); 1245 | if (mDragState == STATE_DRAGGING) { 1246 | // Callback might have started an edge drag. 1247 | break; 1248 | } 1249 | 1250 | final View toCapture = findTopChildUnder((int) x, (int) y); 1251 | if (checkTouchSlop(toCapture, dx, dy) 1252 | && tryCaptureViewForDrag(toCapture, pointerId)) { 1253 | break; 1254 | } 1255 | } 1256 | saveLastMotion(ev); 1257 | } 1258 | break; 1259 | } 1260 | 1261 | case MotionEventCompat.ACTION_POINTER_UP: { 1262 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); 1263 | if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) { 1264 | // Try to find another pointer that's still holding on to 1265 | // the captured view. 1266 | int newActivePointer = INVALID_POINTER; 1267 | final int pointerCount = MotionEventCompat.getPointerCount(ev); 1268 | for (int i = 0; i < pointerCount; i++) { 1269 | final int id = MotionEventCompat.getPointerId(ev, i); 1270 | if (id == mActivePointerId) { 1271 | // This one's going away, skip. 1272 | continue; 1273 | } 1274 | 1275 | final float x = MotionEventCompat.getX(ev, i); 1276 | final float y = MotionEventCompat.getY(ev, i); 1277 | if (findTopChildUnder((int) x, (int) y) == mCapturedView 1278 | && tryCaptureViewForDrag(mCapturedView, id)) { 1279 | newActivePointer = mActivePointerId; 1280 | break; 1281 | } 1282 | } 1283 | 1284 | if (newActivePointer == INVALID_POINTER) { 1285 | // We didn't find another pointer still touching the 1286 | // view, release it. 1287 | releaseViewForPointerUp(); 1288 | } 1289 | } 1290 | clearMotionHistory(pointerId); 1291 | break; 1292 | } 1293 | 1294 | case MotionEvent.ACTION_UP: { 1295 | if (mDragState == STATE_DRAGGING) { 1296 | releaseViewForPointerUp(); 1297 | } 1298 | cancel(); 1299 | break; 1300 | } 1301 | 1302 | case MotionEvent.ACTION_CANCEL: { 1303 | if (mDragState == STATE_DRAGGING) { 1304 | dispatchViewReleased(0, 0); 1305 | } 1306 | cancel(); 1307 | break; 1308 | } 1309 | } 1310 | } 1311 | 1312 | private void reportNewEdgeDrags(float dx, float dy, int pointerId) { 1313 | int dragsStarted = 0; 1314 | if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) { 1315 | dragsStarted |= EDGE_LEFT; 1316 | } 1317 | if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) { 1318 | dragsStarted |= EDGE_TOP; 1319 | } 1320 | if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) { 1321 | dragsStarted |= EDGE_RIGHT; 1322 | } 1323 | if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) { 1324 | dragsStarted |= EDGE_BOTTOM; 1325 | } 1326 | 1327 | if (dragsStarted != 0) { 1328 | mEdgeDragsInProgress[pointerId] |= dragsStarted; 1329 | mCallback.onEdgeDragStarted(dragsStarted, pointerId); 1330 | } 1331 | } 1332 | 1333 | private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) { 1334 | final float absDelta = Math.abs(delta); 1335 | final float absODelta = Math.abs(odelta); 1336 | 1337 | if ((mInitialEdgeTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 1338 | || (mEdgeDragsLocked[pointerId] & edge) == edge 1339 | || (mEdgeDragsInProgress[pointerId] & edge) == edge 1340 | || (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) { 1341 | return false; 1342 | } 1343 | if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) { 1344 | mEdgeDragsLocked[pointerId] |= edge; 1345 | return false; 1346 | } 1347 | return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop; 1348 | } 1349 | 1350 | /** 1351 | * Check if we've crossed a reasonable touch slop for the given child view. 1352 | * If the child cannot be dragged along the horizontal or vertical axis, 1353 | * motion along that axis will not count toward the slop check. 1354 | * 1355 | * @param child Child to check 1356 | * @param dx Motion since initial position along X axis 1357 | * @param dy Motion since initial position along Y axis 1358 | * @return true if the touch slop has been crossed 1359 | */ 1360 | private boolean checkTouchSlop(View child, float dx, float dy) { 1361 | if (child == null) { 1362 | return false; 1363 | } 1364 | final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0; 1365 | final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0; 1366 | 1367 | if (checkHorizontal && checkVertical) { 1368 | return dx * dx + dy * dy > mTouchSlop * mTouchSlop; 1369 | } else if (checkHorizontal) { 1370 | return Math.abs(dx) > mTouchSlop; 1371 | } else if (checkVertical) { 1372 | return Math.abs(dy) > mTouchSlop; 1373 | } 1374 | return false; 1375 | } 1376 | 1377 | /** 1378 | * Check if any pointer tracked in the current gesture has crossed the 1379 | * required slop threshold. 1380 | *

1381 | * This depends on internal state populated by 1382 | * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or 1383 | * {@link #processTouchEvent(android.view.MotionEvent)}. You should only 1384 | * rely on the results of this method after all currently available touch 1385 | * data has been provided to one of these two methods. 1386 | *

1387 | * 1388 | * @param directions Combination of direction flags, see 1389 | * {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL}, 1390 | * {@link #DIRECTION_ALL} 1391 | * @return true if the slop threshold has been crossed, false otherwise 1392 | */ 1393 | public boolean checkTouchSlop(int directions) { 1394 | final int count = mInitialMotionX.length; 1395 | for (int i = 0; i < count; i++) { 1396 | if (checkTouchSlop(directions, i)) { 1397 | return true; 1398 | } 1399 | } 1400 | return false; 1401 | } 1402 | 1403 | /** 1404 | * Check if the specified pointer tracked in the current gesture has crossed 1405 | * the required slop threshold. 1406 | *

1407 | * This depends on internal state populated by 1408 | * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or 1409 | * {@link #processTouchEvent(android.view.MotionEvent)}. You should only 1410 | * rely on the results of this method after all currently available touch 1411 | * data has been provided to one of these two methods. 1412 | *

1413 | * 1414 | * @param directions Combination of direction flags, see 1415 | * {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL}, 1416 | * {@link #DIRECTION_ALL} 1417 | * @param pointerId ID of the pointer to slop check as specified by 1418 | * MotionEvent 1419 | * @return true if the slop threshold has been crossed, false otherwise 1420 | */ 1421 | public boolean checkTouchSlop(int directions, int pointerId) { 1422 | if (!isPointerDown(pointerId)) { 1423 | return false; 1424 | } 1425 | 1426 | final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL; 1427 | final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL; 1428 | 1429 | final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId]; 1430 | final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId]; 1431 | 1432 | if (checkHorizontal && checkVertical) { 1433 | return dx * dx + dy * dy > mTouchSlop * mTouchSlop; 1434 | } else if (checkHorizontal) { 1435 | return Math.abs(dx) > mTouchSlop; 1436 | } else if (checkVertical) { 1437 | return Math.abs(dy) > mTouchSlop; 1438 | } 1439 | return false; 1440 | } 1441 | 1442 | /** 1443 | * Check if any of the edges specified were initially touched in the 1444 | * currently active gesture. If there is no currently active gesture this 1445 | * method will return false. 1446 | * 1447 | * @param edges Edges to check for an initial edge touch. See 1448 | * {@link #EDGE_LEFT}, {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, 1449 | * {@link #EDGE_BOTTOM} and {@link #EDGE_ALL} 1450 | * @return true if any of the edges specified were initially touched in the 1451 | * current gesture 1452 | */ 1453 | public boolean isEdgeTouched(int edges) { 1454 | final int count = mInitialEdgeTouched.length; 1455 | for (int i = 0; i < count; i++) { 1456 | if (isEdgeTouched(edges, i)) { 1457 | return true; 1458 | } 1459 | } 1460 | return false; 1461 | } 1462 | 1463 | /** 1464 | * Check if any of the edges specified were initially touched by the pointer 1465 | * with the specified ID. If there is no currently active gesture or if 1466 | * there is no pointer with the given ID currently down this method will 1467 | * return false. 1468 | * 1469 | * @param edges Edges to check for an initial edge touch. See 1470 | * {@link #EDGE_LEFT}, {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, 1471 | * {@link #EDGE_BOTTOM} and {@link #EDGE_ALL} 1472 | * @return true if any of the edges specified were initially touched in the 1473 | * current gesture 1474 | */ 1475 | public boolean isEdgeTouched(int edges, int pointerId) { 1476 | return isPointerDown(pointerId) && (mInitialEdgeTouched[pointerId] & edges) != 0; 1477 | } 1478 | 1479 | private void releaseViewForPointerUp() { 1480 | mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); 1481 | final float xvel = clampMag( 1482 | VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), 1483 | mMinVelocity, mMaxVelocity); 1484 | final float yvel = clampMag( 1485 | VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId), 1486 | mMinVelocity, mMaxVelocity); 1487 | dispatchViewReleased(xvel, yvel); 1488 | } 1489 | 1490 | private void dragTo(int left, int top, int dx, int dy) { 1491 | int clampedX = left; 1492 | int clampedY = top; 1493 | final int oldLeft = mCapturedView.getLeft(); 1494 | final int oldTop = mCapturedView.getTop(); 1495 | if (dx != 0) { 1496 | clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx); 1497 | mCapturedView.offsetLeftAndRight(clampedX - oldLeft); 1498 | } 1499 | if (dy != 0) { 1500 | clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy); 1501 | mCapturedView.offsetTopAndBottom(clampedY - oldTop); 1502 | } 1503 | 1504 | if (dx != 0 || dy != 0) { 1505 | final int clampedDx = clampedX - oldLeft; 1506 | final int clampedDy = clampedY - oldTop; 1507 | mCallback 1508 | .onViewPositionChanged(mCapturedView, clampedX, clampedY, clampedDx, clampedDy); 1509 | } 1510 | } 1511 | 1512 | /** 1513 | * Determine if the currently captured view is under the given point in the 1514 | * parent view's coordinate system. If there is no captured view this method 1515 | * will return false. 1516 | * 1517 | * @param x X position to test in the parent's coordinate system 1518 | * @param y Y position to test in the parent's coordinate system 1519 | * @return true if the captured view is under the given point, false 1520 | * otherwise 1521 | */ 1522 | public boolean isCapturedViewUnder(int x, int y) { 1523 | return isViewUnder(mCapturedView, x, y); 1524 | } 1525 | 1526 | /** 1527 | * Determine if the supplied view is under the given point in the parent 1528 | * view's coordinate system. 1529 | * 1530 | * @param view Child view of the parent to hit test 1531 | * @param x X position to test in the parent's coordinate system 1532 | * @param y Y position to test in the parent's coordinate system 1533 | * @return true if the supplied view is under the given point, false 1534 | * otherwise 1535 | */ 1536 | public boolean isViewUnder(View view, int x, int y) { 1537 | if (view == null) { 1538 | return false; 1539 | } 1540 | return x >= view.getLeft() && x < view.getRight() && y >= view.getTop() 1541 | && y < view.getBottom(); 1542 | } 1543 | 1544 | /** 1545 | * Find the topmost child under the given point within the parent view's 1546 | * coordinate system. The child order is determined using 1547 | * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#getOrderedChildIndex(int)} 1548 | * . 1549 | * 1550 | * @param x X position to test in the parent's coordinate system 1551 | * @param y Y position to test in the parent's coordinate system 1552 | * @return The topmost child view under (x, y) or null if none found. 1553 | */ 1554 | public View findTopChildUnder(int x, int y) { 1555 | final int childCount = mParentView.getChildCount(); 1556 | for (int i = childCount - 1; i >= 0; i--) { 1557 | final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i)); 1558 | if (x >= child.getLeft() && x < child.getRight() && y >= child.getTop() 1559 | && y < child.getBottom()) { 1560 | return child; 1561 | } 1562 | } 1563 | return null; 1564 | } 1565 | 1566 | private int getEdgeTouched(int x, int y) { 1567 | int result = 0; 1568 | 1569 | if (x < mParentView.getLeft() + mEdgeSize) 1570 | result = EDGE_LEFT; 1571 | if (y < mParentView.getTop() + mEdgeSize) 1572 | result = EDGE_TOP; 1573 | if (x > mParentView.getRight() - mEdgeSize) 1574 | result = EDGE_RIGHT; 1575 | if (y > mParentView.getBottom() - mEdgeSize) 1576 | result = EDGE_BOTTOM; 1577 | 1578 | return result; 1579 | } 1580 | } 1581 | --------------------------------------------------------------------------------