├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zzh12138 │ │ └── reboundlayout │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zzh12138 │ │ │ └── reboundlayout │ │ │ ├── Bean.java │ │ │ ├── Data.java │ │ │ ├── ItemDecoration.java │ │ │ ├── MainActivity.java │ │ │ ├── RVAdapter.java │ │ │ ├── Util.java │ │ │ ├── VPAdapter.java │ │ │ ├── drag │ │ │ ├── CircleImageActivity.java │ │ │ ├── DragActivity.java │ │ │ ├── DragFragment.java │ │ │ ├── DragZoomLayout.java │ │ │ ├── DragZoomLayoutKt.kt │ │ │ └── OnDragDistanceChangeListener.java │ │ │ └── rebound │ │ │ ├── OnBounceDistanceChangeListener.java │ │ │ ├── ReBoundActivity.java │ │ │ ├── ReBoundFragment.java │ │ │ ├── ReBoundLayout.java │ │ │ └── ReBoundLayoutKt.kt │ └── res │ │ ├── anim │ │ ├── animate_alpha_0_1.xml │ │ └── animate_alpha_1_0.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── bg_corner.xml │ │ ├── bg_normal.xml │ │ ├── ic_launcher_background.xml │ │ └── item_decoration.xml │ │ ├── layout │ │ ├── activity_circle_image_acitivy.xml │ │ ├── activity_drag.xml │ │ ├── activity_main.xml │ │ ├── activity_re_bound.xml │ │ ├── adapter_rv.xml │ │ ├── fragment_drag.xml │ │ └── fragment_rebound.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── zzh12138 │ └── reboundlayout │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zzh12138 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 自定义回弹布局 2 | 详情戳[这里](https://www.jianshu.com/p/600380d78779) 3 | ##### 效果一: 4 | ##### ReBoundLayout 5 | **Step 1** 6 | ``` 7 | 17 | 18 | 22 | 23 | ``` 24 | 自定义属性相关: 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ``` 35 | * innerView 移动的View 36 | * resistance 阻力系数 37 | * orientation 回弹方向 38 | * mDuration 回弹时长 39 | * resetDistance 回弹阈值 40 | * isNeedReset 手指抬起时距离大于阈值是否需要回弹 41 | 42 | 也可调用set方法进行修改 43 | **Step 2** 44 | ``` 45 | reBoundLayout.setOnBounceDistanceChangeListener(new OnBounceDistanceChangeListener() { 46 | @Override 47 | public void onDistanceChange(int distance, int direction) { 48 | //移动回调 49 | } 50 | 51 | @Override 52 | public void onFingerUp(int distance, int direction) { 53 | //手指松开回调 54 | } 55 | }); 56 | ``` 57 | >PS:**Kotlin**请使用 **ReBoundLayoutKt** 类 58 | ##### 效果二: 59 | ##### DragZoomLayout 60 | **Step 1** 61 | ``` 62 | 69 | 70 | 74 | 75 | ``` 76 | 自定义属性相关: 77 | ``` 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ``` 86 | * innerView 移动的View 87 | * resistance 阻力系数 88 | * orientation 回弹方向 89 | * mDuration 回弹时长 90 | * resetDistance 回弹阈值 91 | * isNeedReset 手指抬起时距离大于阈值是否需要回弹 92 | * mMinRadius 圆的最小半径 93 | * mMaxRadius 圆的最大半径 94 | 95 | 也可调用set方法进行修改 96 | **Step 2** 97 | ``` 98 | dragLayout.setOnDragDistanceChangeListener(new OnDragDistanceChangeListener() { 99 | @Override 100 | public void onDistanceChange(int translationX, int translationY, int direction) { 101 | //距离改变 102 | } 103 | 104 | @Override 105 | public void onFingerUp(int translationX, int translationY, int direction) { 106 | //手指抬起 107 | } 108 | }); 109 | ``` 110 | >PS:**Kotlin**请使用 **DragZoomLayoutKt** 类 111 | 112 | # License 113 | MIT 114 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 28 6 | defaultConfig { 7 | applicationId "com.zzh12138.reboundlayout" 8 | minSdkVersion 15 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'com.android.support:appcompat-v7:28.0.0' 25 | implementation 'com.android.support:recyclerview-v7:28.0.0' 26 | implementation 'com.jakewharton:butterknife:8.5.1' 27 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' 28 | implementation 'com.github.bumptech.glide:glide:4.4.0' 29 | annotationProcessor 'com.github.bumptech.glide:compiler:4.4.0' 30 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 35 | } 36 | repositories { 37 | mavenCentral() 38 | } 39 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/zzh12138/reboundlayout/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.zzh12138.reboundlayout", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/Bean.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout; 2 | 3 | /** 4 | * Created by zhangzhihao on 2019/1/8 16:43. 5 | */ 6 | public class Bean { 7 | private String title; 8 | private String url; 9 | 10 | public Bean(String title, String url) { 11 | this.title = title; 12 | this.url = url; 13 | } 14 | 15 | public String getTitle() { 16 | return title; 17 | } 18 | 19 | public void setTitle(String title) { 20 | this.title = title; 21 | } 22 | 23 | public String getUrl() { 24 | return url; 25 | } 26 | 27 | public void setUrl(String url) { 28 | this.url = url; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/Data.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout; 2 | 3 | /** 4 | * Created by zhangzhihao on 2019/1/8 16:56. 5 | */ 6 | public class Data { 7 | public static String[] image = new String[]{"http://android-wallpapers.25pp.com/20140415/1446/534cd60d01d7047_900x675.jpg", 8 | "http://cdn.duitang.com/uploads/item/201602/27/20160227181133_YjZVe.jpeg", 9 | "http://img3.imgtn.bdimg.com/it/u=999553423,3288263012&fm=26&gp=0.jpg", 10 | "http://pic27.photophoto.cn/20130630/0036036878082529_b.jpg", 11 | "http://img4.imgtn.bdimg.com/it/u=1995890925,817602913&fm=26&gp=0.jpg", 12 | "http://img1.imgtn.bdimg.com/it/u=3655565462,314827133&fm=26&gp=0.jpg", 13 | "http://img1.imgtn.bdimg.com/it/u=1627185924,1925590495&fm=26&gp=0.jpg", 14 | "http://img0.imgtn.bdimg.com/it/u=3622851037,3121030191&fm=27&gp=0.jpg", 15 | "http://img3.imgtn.bdimg.com/it/u=3159360602,2315537063&fm=27&gp=0.jpg", 16 | "http://img1.imgtn.bdimg.com/it/u=2156236282,1270726641&fm=27&gp=0.jpg" 17 | }; 18 | public static String[] title = new String[]{"凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", 19 | "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", 20 | "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", 21 | "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", 22 | "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", 23 | "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈", "凑字数的标题啦啦啦啦啦啦啦哈哈哈哈哈哈哈"}; 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/ItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Rect; 6 | import android.graphics.drawable.Drawable; 7 | import android.support.annotation.NonNull; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.View; 10 | 11 | 12 | /** 13 | * Created by zhangzhihao on 2019/1/9 16:44. 14 | */ 15 | public class ItemDecoration extends RecyclerView.ItemDecoration { 16 | private Context mContext; 17 | private Drawable mDrawable; 18 | 19 | public ItemDecoration(Context mContext, int resId) { 20 | this.mContext = mContext; 21 | mDrawable = mContext.getResources().getDrawable(resId); 22 | } 23 | 24 | @Override 25 | public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 26 | super.onDraw(c, parent, state); 27 | drawHorizontal(c, parent); 28 | } 29 | 30 | private void drawHorizontal(Canvas c, RecyclerView parent) { 31 | int left = parent.getPaddingLeft(); 32 | int right = parent.getWidth() - parent.getPaddingRight(); 33 | int childCount = parent.getChildCount(); 34 | for (int i = 0; i < childCount; i++) { 35 | View child = parent.getChildAt(i); 36 | RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); 37 | int top = child.getBottom() + params.bottomMargin; 38 | int bottom = top + mDrawable.getIntrinsicHeight(); 39 | mDrawable.setBounds(left, top, right, bottom); 40 | mDrawable.draw(c); 41 | } 42 | } 43 | 44 | @Override 45 | public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 46 | super.getItemOffsets(outRect, view, parent, state); 47 | outRect.set(0, 0, 0, mDrawable.getIntrinsicHeight()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.widget.Button; 8 | 9 | import com.zzh12138.reboundlayout.drag.CircleImageActivity; 10 | import com.zzh12138.reboundlayout.rebound.ReBoundActivity; 11 | 12 | import butterknife.BindView; 13 | import butterknife.ButterKnife; 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | @BindView(R.id.reBound) 18 | Button reBound; 19 | @BindView(R.id.drag) 20 | Button drag; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_main); 26 | ButterKnife.bind(this); 27 | reBound.setOnClickListener(new View.OnClickListener() { 28 | @Override 29 | public void onClick(View v) { 30 | startActivity(new Intent(MainActivity.this, ReBoundActivity.class)); 31 | overridePendingTransition(R.anim.animate_alpha_0_1, 0); 32 | } 33 | }); 34 | 35 | drag.setOnClickListener(new View.OnClickListener() { 36 | @Override 37 | public void onClick(View v) { 38 | startActivity(new Intent(MainActivity.this, CircleImageActivity.class)); 39 | } 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/RVAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | 13 | import com.bumptech.glide.Glide; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * Created by zhangzhihao on 2019/1/8 16:38. 19 | */ 20 | public class RVAdapter extends RecyclerView.Adapter { 21 | private List mList; 22 | private Context mContext; 23 | 24 | public RVAdapter(List mList, Context mContext) { 25 | this.mList = mList; 26 | this.mContext = mContext; 27 | } 28 | 29 | @NonNull 30 | @Override 31 | public MyHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { 32 | return new MyHolder(LayoutInflater.from(mContext).inflate(R.layout.adapter_rv, viewGroup, false)); 33 | } 34 | 35 | @Override 36 | public void onBindViewHolder(@NonNull MyHolder myHolder, int i) { 37 | RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) myHolder.layout.getLayoutParams(); 38 | if (i == 0) { 39 | myHolder.layout.setBackgroundResource(R.drawable.bg_corner); 40 | p.topMargin = 200; 41 | } else { 42 | myHolder.layout.setBackgroundResource(R.drawable.bg_normal); 43 | p.topMargin = 0; 44 | } 45 | Glide.with(mContext).load(mList.get(i).getUrl()).into(myHolder.image); 46 | Glide.with(mContext).load(R.mipmap.ic_launcher).into(myHolder.avatar); 47 | myHolder.title.setText(mList.get(i).getTitle()); 48 | } 49 | 50 | @Override 51 | public int getItemCount() { 52 | return mList == null ? 0 : mList.size(); 53 | } 54 | 55 | class MyHolder extends RecyclerView.ViewHolder { 56 | 57 | private ImageView image; 58 | private ImageView avatar; 59 | private TextView title; 60 | private LinearLayout layout; 61 | 62 | public MyHolder(@NonNull View itemView) { 63 | super(itemView); 64 | avatar = itemView.findViewById(R.id.avatar); 65 | image = itemView.findViewById(R.id.image); 66 | title = itemView.findViewById(R.id.title); 67 | layout = itemView.findViewById(R.id.layout); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/Util.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Created by zhangzhihao on 2019/1/9 15:06. 7 | */ 8 | public class Util { 9 | public static int dipTopx(Context context, float dpValue) { 10 | float density = context.getResources().getDisplayMetrics().density; 11 | return (int) (dpValue * density + 0.5); 12 | } 13 | public static int getScreenWidth(Context context) { 14 | return context.getResources().getDisplayMetrics().widthPixels; 15 | } 16 | public static int getScreenHeight(Context context) { 17 | return context.getResources().getDisplayMetrics().heightPixels; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/VPAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentStatePagerAdapter; 6 | import android.support.v7.widget.RecyclerView; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Created by zhangzhihao on 2019/1/8 15:54. 12 | */ 13 | public class VPAdapter extends FragmentStatePagerAdapter { 14 | 15 | private List mList; 16 | 17 | public VPAdapter(FragmentManager fm, List list) { 18 | super(fm); 19 | mList = list; 20 | } 21 | 22 | @Override 23 | public Fragment getItem(int i) { 24 | return mList.get(i); 25 | } 26 | 27 | @Override 28 | public int getCount() { 29 | return mList == null ? 0 : mList.size(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/drag/CircleImageActivity.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.drag; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Point; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | import android.view.ViewTreeObserver; 9 | import android.widget.ImageView; 10 | 11 | import com.bumptech.glide.Glide; 12 | import com.bumptech.glide.request.RequestOptions; 13 | import com.zzh12138.reboundlayout.Data; 14 | import com.zzh12138.reboundlayout.R; 15 | import com.zzh12138.reboundlayout.Util; 16 | 17 | import butterknife.BindView; 18 | import butterknife.ButterKnife; 19 | 20 | public class CircleImageActivity extends AppCompatActivity { 21 | 22 | @BindView(R.id.image) 23 | ImageView image; 24 | 25 | private int[] location = new int[2]; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_circle_image_acitivy); 31 | ButterKnife.bind(this); 32 | Glide.with(this).load(Data.image[0]).apply(new RequestOptions().circleCrop()).into(image); 33 | image.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 34 | @Override 35 | public boolean onPreDraw() { 36 | image.getViewTreeObserver().removeOnPreDrawListener(this); 37 | image.getLocationOnScreen(location); 38 | return true; 39 | } 40 | }); 41 | image.setOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | Intent intent = new Intent(CircleImageActivity.this, DragActivity.class); 45 | Point point = new Point(location[0] 46 | , location[1]); 47 | intent.putExtra("point", point); 48 | startActivity(intent); 49 | overridePendingTransition(0, 0); 50 | } 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/drag/DragActivity.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.drag; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.graphics.Color; 5 | import android.graphics.Point; 6 | import android.os.Bundle; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v4.view.ViewPager; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.view.ViewTreeObserver; 11 | 12 | import com.zzh12138.reboundlayout.R; 13 | import com.zzh12138.reboundlayout.Util; 14 | import com.zzh12138.reboundlayout.VPAdapter; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import butterknife.BindView; 20 | import butterknife.ButterKnife; 21 | 22 | import static com.zzh12138.reboundlayout.rebound.OnBounceDistanceChangeListener.DIRECTION_DOWN; 23 | import static com.zzh12138.reboundlayout.rebound.OnBounceDistanceChangeListener.DIRECTION_LEFT; 24 | import static com.zzh12138.reboundlayout.rebound.OnBounceDistanceChangeListener.DIRECTION_RIGHT; 25 | 26 | public class DragActivity extends AppCompatActivity implements OnDragDistanceChangeListener { 27 | private static final String TAG = "DragActivity"; 28 | 29 | @BindView(R.id.viewpager) 30 | ViewPager viewpager; 31 | @BindView(R.id.dragLayout) 32 | DragZoomLayout dragLayout; 33 | private List mList; 34 | private VPAdapter mAdapter; 35 | 36 | private Point point; 37 | private int[] location = new int[2]; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_drag); 43 | ButterKnife.bind(this); 44 | mList = new ArrayList<>(5); 45 | for (int i = 0; i < 5; i++) { 46 | mList.add(new DragFragment()); 47 | } 48 | mAdapter = new VPAdapter(getSupportFragmentManager(), mList); 49 | viewpager.setAdapter(mAdapter); 50 | viewpager.setPageMargin(Util.dipTopx(this, 20)); 51 | dragLayout.setMinRadius(Util.dipTopx(this, 25)); 52 | dragLayout.setResistance(2f); 53 | dragLayout.setResetDistance(Util.getScreenWidth(this) / 5); 54 | dragLayout.setOnDragDistanceChangeListener(this); 55 | point = getIntent().getParcelableExtra("point"); 56 | dragLayout.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 57 | @Override 58 | public boolean onPreDraw() { 59 | dragLayout.getViewTreeObserver().removeOnPreDrawListener(this); 60 | dragLayout.getLocationOnScreen(location); 61 | dragLayout.enterAnimate((point.x - (location[0] + dragLayout.getWidth() / 2 - dragLayout.getMinRadius())), 62 | point.y - (location[1] + dragLayout.getHeight() / 2 - dragLayout.getMinRadius())); 63 | dragLayout.finishAnimate(0, 0, dragLayout.getMaxRadius()); 64 | //修改背景透明度 65 | ValueAnimator animator = ValueAnimator.ofInt(0, 128); 66 | animator.setDuration(300); 67 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 68 | @Override 69 | public void onAnimationUpdate(ValueAnimator animation) { 70 | dragLayout.setBackgroundColor(Color.argb((Integer) animation.getAnimatedValue(), 0, 0, 0)); 71 | } 72 | }); 73 | animator.start(); 74 | return true; 75 | } 76 | }); 77 | } 78 | 79 | @Override 80 | public void onDistanceChange(int translationX, int translationY, int direction) { 81 | switch (direction) { 82 | case DIRECTION_LEFT: 83 | case DIRECTION_RIGHT: 84 | float percent = Math.abs(translationX * 1f) / dragLayout.getLargeX(); 85 | if (percent > 1) { 86 | percent = 1; 87 | } 88 | int a = (int) (128 * (1 - percent)); 89 | dragLayout.setBackgroundColor(Color.argb(a, 0, 0, 0)); 90 | break; 91 | case DIRECTION_DOWN: 92 | float p = Math.abs(translationY * 1f) / dragLayout.getLargeY(); 93 | if (p > 1) { 94 | p = 1; 95 | } 96 | int alpha = (int) (128 * (1 - p)); 97 | dragLayout.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); 98 | dragLayout.changeTranslationAndSize(translationX, translationY); 99 | break; 100 | default: 101 | break; 102 | } 103 | } 104 | 105 | @Override 106 | public void onFingerUp(int translationX, int translationY, int direction) { 107 | switch (direction) { 108 | case DIRECTION_LEFT: 109 | case DIRECTION_RIGHT: 110 | if (translationX > dragLayout.getResetDistance()) { 111 | dragLayout.finishAnimate((point.x - (location[0] + dragLayout.getWidth() / 2 - dragLayout.getMinRadius())), 112 | point.y - (location[1] + dragLayout.getHeight() / 2 - dragLayout.getMinRadius()), dragLayout.getMinRadius()); 113 | dragLayout.postDelayed(new Runnable() { 114 | @Override 115 | public void run() { 116 | finish(); 117 | } 118 | }, dragLayout.getDuration()); 119 | } 120 | break; 121 | case DIRECTION_DOWN: 122 | if (translationY > dragLayout.getResetDistance() * 2) { 123 | dragLayout.finishAnimate((point.x - (location[0] + dragLayout.getWidth() / 2 - dragLayout.getMinRadius())), 124 | point.y - (location[1] + dragLayout.getHeight() / 2 - dragLayout.getMinRadius()), dragLayout.getMinRadius()); 125 | dragLayout.postDelayed(new Runnable() { 126 | @Override 127 | public void run() { 128 | finish(); 129 | } 130 | }, dragLayout.getDuration()); 131 | } else { 132 | dragLayout.finishAnimate(0, 0, dragLayout.getMaxRadius()); 133 | } 134 | break; 135 | default: 136 | break; 137 | } 138 | } 139 | 140 | @Override 141 | public void finish() { 142 | super.finish(); 143 | overridePendingTransition(0, R.anim.animate_alpha_1_0); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/drag/DragFragment.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.drag; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import com.zzh12138.reboundlayout.Bean; 15 | import com.zzh12138.reboundlayout.Data; 16 | import com.zzh12138.reboundlayout.R; 17 | import com.zzh12138.reboundlayout.RVAdapter; 18 | import com.zzh12138.reboundlayout.Util; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * Created by zhangzhihao on 2019/1/10 11:00. 25 | */ 26 | public class DragFragment extends Fragment implements OnDragDistanceChangeListener { 27 | private static final String TAG = "DragFragment"; 28 | 29 | private View mView; 30 | private RecyclerView mRecycler; 31 | private List mList; 32 | private RVAdapter mAdapter; 33 | private DragZoomLayout mLayout; 34 | private OnDragDistanceChangeListener onDragDistanceChangeListener; 35 | 36 | @Override 37 | public void onAttach(Context context) { 38 | super.onAttach(context); 39 | if (context instanceof DragActivity) { 40 | onDragDistanceChangeListener = (OnDragDistanceChangeListener) context; 41 | } 42 | } 43 | 44 | @Nullable 45 | @Override 46 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 47 | if (mView == null) { 48 | mView = inflater.inflate(R.layout.fragment_drag, container, false); 49 | mRecycler = mView.findViewById(R.id.recycler); 50 | mLayout = mView.findViewById(R.id.layout); 51 | mList = new ArrayList<>(10); 52 | for (int i = 0; i < 10; i++) { 53 | mList.add(new Bean(Data.title[i] + i, Data.image[i])); 54 | } 55 | mAdapter = new RVAdapter(mList, getContext()); 56 | mRecycler.setAdapter(mAdapter); 57 | mRecycler.setLayoutManager(new LinearLayoutManager(getContext())); 58 | mLayout.setOnDragDistanceChangeListener(this); 59 | } else { 60 | ViewGroup parent = (ViewGroup) mView.getParent(); 61 | if (parent != null) { 62 | parent.removeView(mView); 63 | } 64 | } 65 | return mView; 66 | } 67 | 68 | @Override 69 | public void onDistanceChange(int translationX, int translationY, int direction) { 70 | if (onDragDistanceChangeListener != null) { 71 | onDragDistanceChangeListener.onDistanceChange(translationX, translationY, direction); 72 | } 73 | } 74 | 75 | @Override 76 | public void onFingerUp(int translationX, int translationY, int direction) { 77 | if (onDragDistanceChangeListener != null) { 78 | onDragDistanceChangeListener.onFingerUp(translationX, translationY, direction); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/drag/DragZoomLayout.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.drag; 2 | 3 | import android.animation.PropertyValuesHolder; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Canvas; 8 | import android.graphics.Path; 9 | import android.graphics.Point; 10 | import android.support.annotation.NonNull; 11 | import android.support.annotation.Nullable; 12 | import android.util.AttributeSet; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | import android.view.ViewConfiguration; 16 | import android.view.ViewParent; 17 | import android.view.animation.AccelerateDecelerateInterpolator; 18 | import android.view.animation.Interpolator; 19 | import android.view.animation.LinearInterpolator; 20 | import android.widget.FrameLayout; 21 | import android.widget.LinearLayout; 22 | 23 | import com.zzh12138.reboundlayout.R; 24 | 25 | import static com.zzh12138.reboundlayout.rebound.OnBounceDistanceChangeListener.DIRECTION_DOWN; 26 | import static com.zzh12138.reboundlayout.rebound.OnBounceDistanceChangeListener.DIRECTION_LEFT; 27 | import static com.zzh12138.reboundlayout.rebound.OnBounceDistanceChangeListener.DIRECTION_RIGHT; 28 | import static com.zzh12138.reboundlayout.rebound.OnBounceDistanceChangeListener.DIRECTION_UP; 29 | 30 | /** 31 | * Created by zhangzhihao on 2019/1/9 18:00. 32 | */ 33 | public class DragZoomLayout extends FrameLayout { 34 | private static final String TAG = "DragZoomLayout"; 35 | 36 | private int mTouchSlop; 37 | private int mDownX; 38 | private int mDownY; 39 | private boolean isIntercept; 40 | private View innerView; 41 | private float resistance; 42 | private int orientation; 43 | private long mDuration; 44 | private Interpolator mInterpolator; 45 | private int resetDistance; 46 | private OnDragDistanceChangeListener onDragDistanceChangeListener; 47 | private int mMinRadius; 48 | private int mMaxRadius; 49 | private int mRadius; 50 | private Path mPath; 51 | private int mTranslationX; 52 | private int mTranslationY; 53 | private int mLargeX; 54 | private int mLargeY; 55 | private Point mPoint; 56 | 57 | public DragZoomLayout(@NonNull Context context) { 58 | this(context, null); 59 | } 60 | 61 | public DragZoomLayout(@NonNull Context context, @Nullable AttributeSet attrs) { 62 | this(context, attrs, 0); 63 | } 64 | 65 | public DragZoomLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 66 | super(context, attrs, defStyleAttr); 67 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DragZoomLayout); 68 | orientation = array.getInt(R.styleable.DragZoomLayout_interceptOrientation, LinearLayout.HORIZONTAL); 69 | mDuration = array.getInteger(R.styleable.DragZoomLayout_reSetDuration, 300); 70 | array.recycle(); 71 | mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 72 | mInterpolator = new LinearInterpolator(); 73 | resetDistance = Integer.MAX_VALUE; 74 | resistance = 1f; 75 | mPath = new Path(); 76 | } 77 | 78 | @Override 79 | public boolean onInterceptTouchEvent(MotionEvent ev) { 80 | switch (ev.getActionMasked()) { 81 | case MotionEvent.ACTION_DOWN: 82 | if (innerView != null) { 83 | innerView.clearAnimation(); 84 | } 85 | mDownX = (int) ev.getX(); 86 | mDownY = (int) ev.getY(); 87 | break; 88 | case MotionEvent.ACTION_MOVE: 89 | int difX = (int) ev.getX() - mDownX; 90 | int difY = (int) ev.getY() - mDownY; 91 | if (orientation == LinearLayout.HORIZONTAL) { 92 | if (Math.abs(difX) > mTouchSlop && Math.abs(difX) > Math.abs(difY)) { 93 | ViewParent parent = getParent(); 94 | while (parent != null) { 95 | parent.requestDisallowInterceptTouchEvent(true); 96 | parent = parent.getParent(); 97 | isIntercept = true; 98 | } 99 | if (!innerView.canScrollHorizontally(-1) && difX > 0) { 100 | //右啦到边界 101 | return true; 102 | } 103 | if (!innerView.canScrollHorizontally(1) && difX < 0) { 104 | //左啦到边界 105 | return true; 106 | } 107 | } 108 | } else { 109 | if (Math.abs(difY) > mTouchSlop && Math.abs(difY) > Math.abs(difX)) { 110 | ViewParent parent = getParent(); 111 | while (parent != null) { 112 | parent.requestDisallowInterceptTouchEvent(true); 113 | parent = parent.getParent(); 114 | isIntercept = true; 115 | } 116 | if (!innerView.canScrollVertically(-1) && difY > 0) { 117 | //下拉到边界 118 | return true; 119 | } 120 | if (!innerView.canScrollVertically(1) && difY < 0) { 121 | //上啦到边界 122 | return true; 123 | } 124 | } 125 | } 126 | break; 127 | case MotionEvent.ACTION_UP: 128 | case MotionEvent.ACTION_CANCEL: 129 | if (isIntercept) { 130 | ViewParent parent = getParent(); 131 | while (parent != null) { 132 | parent.requestDisallowInterceptTouchEvent(false); 133 | parent = parent.getParent(); 134 | } 135 | } 136 | isIntercept = false; 137 | mDownY = 0; 138 | mDownX = 0; 139 | break; 140 | default: 141 | break; 142 | } 143 | return super.onInterceptTouchEvent(ev); 144 | } 145 | 146 | @Override 147 | public boolean onTouchEvent(MotionEvent event) { 148 | switch (event.getActionMasked()) { 149 | case MotionEvent.ACTION_MOVE: 150 | int difX = (int) ((event.getX() - mDownX) / resistance); 151 | int difY = (int) ((event.getY() - mDownY) / resistance); 152 | if (orientation == LinearLayout.HORIZONTAL) { 153 | boolean needDrag = false; 154 | if (!innerView.canScrollHorizontally(-1) && difX > 0) { 155 | //右啦到边界 156 | needDrag = true; 157 | } else if (!innerView.canScrollHorizontally(1) && difX < 0) { 158 | //左拉到边界 159 | needDrag = true; 160 | } 161 | if (needDrag) { 162 | mRadius = (int) (mMaxRadius * (1 - Math.abs(difX) * 1f / mLargeX)); 163 | if (mRadius < mMinRadius) { 164 | mRadius = mMinRadius; 165 | } else if (mRadius > mMaxRadius) { 166 | mRadius = mMaxRadius; 167 | } 168 | mTranslationX = difX; 169 | mTranslationY = difY; 170 | invalidate(); 171 | if (onDragDistanceChangeListener != null) { 172 | onDragDistanceChangeListener.onDistanceChange(mTranslationX, mTranslationY, mTranslationX > 0 ? DIRECTION_RIGHT : DIRECTION_LEFT); 173 | } 174 | return true; 175 | } 176 | } else { 177 | if (!innerView.canScrollVertically(-1) && difY > 0) { 178 | //下拉到边界 179 | if (onDragDistanceChangeListener != null) { 180 | onDragDistanceChangeListener.onDistanceChange(difX, difY, DIRECTION_DOWN); 181 | } 182 | return true; 183 | } else if (!innerView.canScrollVertically(1) && difY < 0) { 184 | //上啦到边界 185 | innerView.setTranslationY(difY); 186 | return true; 187 | } 188 | } 189 | break; 190 | case MotionEvent.ACTION_CANCEL: 191 | case MotionEvent.ACTION_UP: 192 | if (orientation == LinearLayout.HORIZONTAL) { 193 | //水平 194 | if (Math.abs(mTranslationX) <= resetDistance) { 195 | //重置状态 196 | finishAnimate(0, 0, mMaxRadius); 197 | } 198 | if (onDragDistanceChangeListener != null) { 199 | onDragDistanceChangeListener.onFingerUp(mTranslationX, mTranslationY, mTranslationX > 0 ? DIRECTION_RIGHT : DIRECTION_LEFT); 200 | } 201 | } else { 202 | //竖直 203 | if (innerView.getTranslationY() < 0) { 204 | innerView.animate().setDuration(mDuration).translationY(0).setInterpolator(mInterpolator); 205 | } else { 206 | int x = (int) ((event.getX() - mDownX) / resistance); 207 | int y = (int) ((event.getY() - mDownY) / resistance); 208 | if (onDragDistanceChangeListener != null) { 209 | onDragDistanceChangeListener.onFingerUp(x, y, y > 0 ? DIRECTION_DOWN : DIRECTION_UP); 210 | } 211 | } 212 | } 213 | break; 214 | default: 215 | break; 216 | } 217 | return super.onTouchEvent(event); 218 | } 219 | 220 | 221 | public void finishAnimate(int endX, int endY, int endRadius) { 222 | PropertyValuesHolder animateX = PropertyValuesHolder.ofInt("mTranslationX", mTranslationX, endX); 223 | PropertyValuesHolder animateY = PropertyValuesHolder.ofInt("mTranslationY", mTranslationY, endY); 224 | PropertyValuesHolder animateRadius = PropertyValuesHolder.ofInt("radius", mRadius, endRadius); 225 | ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(animateX, animateY, animateRadius); 226 | animator.setDuration(mDuration); 227 | animator.setInterpolator(mInterpolator); 228 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 229 | @Override 230 | public void onAnimationUpdate(ValueAnimator animation) { 231 | mTranslationX = (int) animation.getAnimatedValue("mTranslationX"); 232 | mTranslationY = (int) animation.getAnimatedValue("mTranslationY"); 233 | mRadius = (int) animation.getAnimatedValue("radius"); 234 | postInvalidate(); 235 | } 236 | }); 237 | animator.start(); 238 | } 239 | 240 | public void changeTranslationAndSize(int translationX, int translationY) { 241 | mTranslationX = translationX; 242 | mTranslationY = translationY; 243 | mRadius = (int) (mMaxRadius * (1 - Math.abs(translationY) * 1f / mLargeY)); 244 | if (mRadius < mMinRadius) { 245 | mRadius = mMinRadius; 246 | } else if (mRadius > mMaxRadius) { 247 | mRadius = mMaxRadius; 248 | } 249 | postInvalidate(); 250 | } 251 | 252 | public void enterAnimate(int translationX, int translationY) { 253 | mTranslationX = translationX; 254 | mTranslationY = translationY; 255 | mRadius = mMinRadius; 256 | postInvalidate(); 257 | } 258 | 259 | @Override 260 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 261 | super.onSizeChanged(w, h, oldw, oldh); 262 | mRadius = mMaxRadius = (int) Math.sqrt(Math.pow(w / 2f, 2) + Math.pow(h / 2f, 2)); 263 | mLargeX = (int) (w / 2f - mMinRadius); 264 | mLargeY = (int) (h / 2f - mMinRadius); 265 | mPoint = new Point((int) (w / 2f), (int) (h / 2f)); 266 | } 267 | 268 | @Override 269 | protected void onDraw(Canvas canvas) { 270 | if (Math.abs(mTranslationX) > mLargeX) { 271 | mTranslationX = mTranslationX > 0 ? mLargeX : -mLargeX; 272 | } 273 | if (Math.abs(mTranslationY) > mLargeY) { 274 | mTranslationY = mTranslationY > 0 ? mLargeY : -mLargeY; 275 | } 276 | canvas.translate(mTranslationX, mTranslationY); 277 | mPath.reset(); 278 | mPath.addCircle(mPoint.x, mPoint.y, mRadius, Path.Direction.CCW); 279 | canvas.clipPath(mPath); 280 | super.onDraw(canvas); 281 | } 282 | 283 | @Override 284 | protected void onFinishInflate() { 285 | super.onFinishInflate(); 286 | if (getChildCount() > 0) { 287 | innerView = getChildAt(0); 288 | } else { 289 | throw new IllegalArgumentException("it must have innerView"); 290 | } 291 | } 292 | 293 | public void setInnerView(View innerView) { 294 | this.innerView = innerView; 295 | } 296 | 297 | public void setOnDragDistanceChangeListener(OnDragDistanceChangeListener 298 | onDragDistanceChangeListener) { 299 | this.onDragDistanceChangeListener = onDragDistanceChangeListener; 300 | } 301 | 302 | public void setMinRadius(int mMinRadius) { 303 | this.mMinRadius = mMinRadius; 304 | } 305 | 306 | public void setResistance(float resistance) { 307 | if (resistance < 1f) { 308 | resistance = 1f; 309 | } 310 | this.resistance = resistance; 311 | } 312 | 313 | public void setResetDistance(int resetDistance) { 314 | this.resetDistance = resetDistance; 315 | } 316 | 317 | public int getMinRadius() { 318 | return mMinRadius; 319 | } 320 | 321 | public long getDuration() { 322 | return mDuration; 323 | } 324 | 325 | public int getLargeX() { 326 | return mLargeX; 327 | } 328 | 329 | public int getLargeY() { 330 | return mLargeY; 331 | } 332 | 333 | public int getResetDistance() { 334 | return resetDistance; 335 | } 336 | 337 | public int getMaxRadius() { 338 | return mMaxRadius; 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/drag/DragZoomLayoutKt.kt: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.drag 2 | 3 | import android.animation.PropertyValuesHolder 4 | import android.animation.ValueAnimator 5 | import android.content.Context 6 | import android.graphics.Canvas 7 | import android.graphics.Path 8 | import android.graphics.Point 9 | import android.util.AttributeSet 10 | import android.view.MotionEvent 11 | import android.view.View 12 | import android.view.ViewConfiguration 13 | import android.view.animation.LinearInterpolator 14 | import android.widget.FrameLayout 15 | import android.widget.LinearLayout 16 | import com.zzh12138.reboundlayout.R 17 | import com.zzh12138.reboundlayout.rebound.OnBounceDistanceChangeListener.* 18 | import java.lang.IllegalArgumentException 19 | 20 | /** 21 | * Created by zhangzhihao on 2019/1/12 16:52. 22 | */ 23 | class DragZoomLayoutKt @JvmOverloads constructor( 24 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 25 | ) : FrameLayout(context, attrs, defStyleAttr) { 26 | var mTouchSlop = 0 27 | var mDownX = 0 28 | var mDownY = 0 29 | var isIntercept = false 30 | var innerView: View? = null 31 | var resistance = 1.0f 32 | var orientation = LinearLayout.HORIZONTAL 33 | var mInterpolator = LinearInterpolator() 34 | var resetDistance = 0 35 | var onDragDistanceChangeListener: OnDragDistanceChangeListener? = null 36 | var mMinRadius = 0 37 | var mMaxRadius = 0 38 | var mRadius = 0 39 | var mPath = Path() 40 | var mTranslationX = 0 41 | var mTranslationY = 0 42 | var mLargeX = 0 43 | var mLargeY = 0 44 | var mPoint = Point() 45 | var mDuration = 0 46 | 47 | init { 48 | if (attrs != null) { 49 | val arr = context.obtainStyledAttributes(attrs, R.styleable.DragZoomLayoutKt) 50 | orientation = arr.getInt(R.styleable.DragZoomLayoutKt_interceptOrientationKt, LinearLayout.HORIZONTAL) 51 | mDuration = arr.getInteger(R.styleable.DragZoomLayoutKt_reSetDurationKt, 300) 52 | arr.recycle() 53 | mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop 54 | resetDistance = Int.MAX_VALUE 55 | resistance = 1f 56 | } 57 | } 58 | 59 | override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { 60 | innerView?.let { 61 | when (ev?.actionMasked) { 62 | MotionEvent.ACTION_DOWN -> { 63 | it.clearAnimation() 64 | mDownX = ev.x.toInt() 65 | mDownY = ev.x.toInt() 66 | } 67 | MotionEvent.ACTION_MOVE -> { 68 | val difX = ev.x - mDownX 69 | val difY = ev.y - mDownY 70 | if (orientation == LinearLayout.HORIZONTAL) { 71 | if (Math.abs(difX) > mTouchSlop && Math.abs(difX) > Math.abs(difY)) { 72 | var parent = parent 73 | while (parent != null) { 74 | parent.requestDisallowInterceptTouchEvent(true) 75 | parent = parent.parent 76 | isIntercept = true 77 | } 78 | if (!it.canScrollHorizontally(-1) && difX > 0) { 79 | //右啦到边界 80 | return true 81 | } 82 | if (!it.canScrollHorizontally(1) && difX < 0) { 83 | //左拉到边界 84 | return true 85 | } 86 | } 87 | } else { 88 | if (Math.abs(difY) > mTouchSlop && Math.abs(difY) > Math.abs(difX)) { 89 | var parent = parent 90 | while (parent != null) { 91 | parent.requestDisallowInterceptTouchEvent(true) 92 | parent = parent.parent 93 | isIntercept = true 94 | } 95 | if (!it.canScrollVertically(-1) && difY > 0) { 96 | //下拉到边界 97 | return true 98 | } 99 | if (!it.canScrollVertically(1) && difY > 0) { 100 | //上啦到边界 101 | return true 102 | } 103 | } 104 | } 105 | } 106 | MotionEvent.ACTION_UP, 107 | MotionEvent.ACTION_CANCEL -> { 108 | if (isIntercept) { 109 | var parent = parent 110 | while (parent != null) { 111 | parent.requestDisallowInterceptTouchEvent(false) 112 | parent = parent.parent 113 | } 114 | } 115 | isIntercept = false 116 | mDownY = 0 117 | mDownX = 0 118 | } 119 | } 120 | } 121 | return super.onInterceptTouchEvent(ev) 122 | } 123 | 124 | override fun onTouchEvent(event: MotionEvent?): Boolean { 125 | innerView?.let { 126 | when (event?.actionMasked) { 127 | MotionEvent.ACTION_MOVE -> { 128 | val difX = event.x - mDownX 129 | val difY = event.y - mDownY 130 | if (orientation == LinearLayout.HORIZONTAL) { 131 | var needDrag = false 132 | if (!it.canScrollHorizontally(-1) && difX > 0) { 133 | //右啦到边界 134 | needDrag = true 135 | } 136 | if (!it.canScrollHorizontally(1) && difX < 0) { 137 | //左拉到边界 138 | needDrag = true 139 | } 140 | if (needDrag) { 141 | mRadius = mMaxRadius * (1 - Math.abs(difX) / mLargeX).toInt() 142 | limitRadius() 143 | mTranslationX = difX.toInt() 144 | mTranslationY = difY.toInt() 145 | invalidate() 146 | onDragDistanceChangeListener?.onDistanceChange(mTranslationX, mTranslationY, 147 | if (mTranslationX > 0) DIRECTION_RIGHT else DIRECTION_LEFT) 148 | return true 149 | }else{ 150 | 151 | } 152 | } else { 153 | if (!it.canScrollVertically(-1) && difY > 0) { 154 | //下拉到边界 155 | onDragDistanceChangeListener?.onDistanceChange(difX.toInt(), difY.toInt(), DIRECTION_DOWN) 156 | return true 157 | }else{ 158 | 159 | } 160 | if (!it.canScrollVertically(1) && difY > 0) { 161 | //上啦到边界 162 | it.translationY = difY 163 | return true 164 | }else{ 165 | 166 | } 167 | } 168 | } 169 | MotionEvent.ACTION_UP, 170 | MotionEvent.ACTION_CANCEL -> { 171 | if (orientation == LinearLayout.HORIZONTAL) { 172 | if (Math.abs(mTranslationX) < resetDistance) { 173 | //重置状态 174 | finishAnimate(0, 0, mMaxRadius) 175 | } 176 | onDragDistanceChangeListener?.onFingerUp(mTranslationX, mTranslationY, 177 | if (mTranslationX > 0) DIRECTION_RIGHT else DIRECTION_LEFT) 178 | } else { 179 | if (it.translationY < 0) { 180 | it.animate().setDuration(mDuration.toLong()).translationY(0f).setInterpolator(mInterpolator) 181 | } else { 182 | val x = (event.x - mDownX) / resistance 183 | val y = (event.y - mDownY) / resistance 184 | onDragDistanceChangeListener?.onFingerUp(x.toInt(), y.toInt(), DIRECTION_DOWN) 185 | } 186 | } 187 | } 188 | else -> { 189 | } 190 | } 191 | } 192 | return super.onTouchEvent(event) 193 | } 194 | 195 | fun limitRadius() { 196 | if (mRadius > mMaxRadius) { 197 | mRadius = mMaxRadius 198 | } else if (mRadius < mMinRadius) { 199 | mRadius = mMinRadius 200 | } 201 | } 202 | 203 | fun finishAnimate(endX: Int, endY: Int, endRadius: Int) { 204 | val animateX = PropertyValuesHolder.ofInt("mTranslationX", mTranslationX, endX) 205 | val animateY = PropertyValuesHolder.ofInt("mTranslationY", mTranslationY, endY) 206 | val animateRadius = PropertyValuesHolder.ofInt("radius", mRadius, endRadius) 207 | val animator = ValueAnimator.ofPropertyValuesHolder(animateX, animateY, animateRadius) 208 | animator.duration = mDuration.toLong() 209 | animator.interpolator = mInterpolator 210 | animator.addUpdateListener { 211 | mTranslationX = it.getAnimatedValue("mTranslationX") as Int 212 | mTranslationY = it.getAnimatedValue("mTranslationY") as Int 213 | mRadius = it.getAnimatedValue("radius") as Int 214 | postInvalidate() 215 | } 216 | animator.start() 217 | } 218 | 219 | fun changeTranslationAndSize(translationX: Int, translationY: Int) { 220 | mTranslationX = translationX 221 | mTranslationY = translationY 222 | mRadius = (mMaxRadius * (1 - Math.abs(translationY).toFloat() / mLargeY)).toInt() 223 | limitRadius() 224 | postInvalidate() 225 | } 226 | 227 | fun enterAnimate(translationX: Int, translationY: Int) { 228 | mTranslationX = translationX 229 | mTranslationY = translationY 230 | mRadius = mMinRadius 231 | postInvalidate() 232 | } 233 | 234 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 235 | super.onSizeChanged(w, h, oldw, oldh) 236 | mRadius = Math.sqrt(Math.pow(w.toDouble() / 2, 2.0) + Math.pow(h.toDouble() / 2, 2.0)).toInt() 237 | mMaxRadius = mRadius 238 | mLargeX = w / 2 - mMinRadius 239 | mLargeY = h / 2 - mMinRadius 240 | mPoint.x = w / 2 241 | mPoint.y = h / 2 242 | } 243 | 244 | override fun onDraw(canvas: Canvas?) { 245 | canvas?.let { 246 | if (Math.abs(mTranslationX) > mLargeX) { 247 | mTranslationX = if (mTranslationX > 0) mLargeX else -mLargeX 248 | } 249 | if (Math.abs(mTranslationY) > mLargeY) { 250 | mTranslationY = if (mTranslationY > 0) mLargeY else -mLargeY 251 | } 252 | canvas.translate(mTranslationX.toFloat(), mTranslationY.toFloat()) 253 | mPath.reset() 254 | mPath.addCircle(mPoint.x.toFloat(), mPoint.y.toFloat(), mRadius.toFloat(), Path.Direction.CCW) 255 | canvas.clipPath(mPath) 256 | } 257 | super.onDraw(canvas) 258 | } 259 | 260 | override fun onFinishInflate() { 261 | super.onFinishInflate() 262 | if(childCount>0){ 263 | innerView=getChildAt(0) 264 | }else{ 265 | throw IllegalArgumentException("it must have innerView") 266 | } 267 | } 268 | 269 | 270 | 271 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/drag/OnDragDistanceChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.drag; 2 | 3 | /** 4 | * Created by zhangzhihao on 2019/1/10 15:10. 5 | */ 6 | public interface OnDragDistanceChangeListener { 7 | void onDistanceChange(int translationX, int translationY, int direction); 8 | 9 | void onFingerUp(int translationX, int translationY, int direction); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/rebound/OnBounceDistanceChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.rebound; 2 | 3 | /** 4 | * Created by zhangzhihao on 2019/1/8 18:27. 5 | */ 6 | public interface OnBounceDistanceChangeListener { 7 | int DIRECTION_LEFT = 1; 8 | int DIRECTION_RIGHT = 2; 9 | int DIRECTION_UP = 3; 10 | int DIRECTION_DOWN = 4; 11 | 12 | void onDistanceChange(int distance, int direction); 13 | 14 | void onFingerUp(int distance, int direction); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/rebound/ReBoundActivity.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.rebound; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.view.ViewPager; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | import android.view.animation.AccelerateInterpolator; 9 | import android.widget.TextView; 10 | 11 | import com.zzh12138.reboundlayout.R; 12 | import com.zzh12138.reboundlayout.Util; 13 | import com.zzh12138.reboundlayout.VPAdapter; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import butterknife.BindView; 19 | import butterknife.ButterKnife; 20 | 21 | public class ReBoundActivity extends AppCompatActivity implements OnBounceDistanceChangeListener { 22 | 23 | @BindView(R.id.viewpager) 24 | ViewPager viewpager; 25 | @BindView(R.id.reBoundLayout) 26 | ReBoundLayout reBoundLayout; 27 | @BindView(R.id.topTip) 28 | TextView topTip; 29 | @BindView(R.id.leftTip) 30 | TextView leftTip; 31 | @BindView(R.id.right_tip) 32 | TextView rightTip; 33 | private VPAdapter mAdapter; 34 | private List mList; 35 | private int mResetDistance; 36 | private int showTipDistance; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_re_bound); 42 | ButterKnife.bind(this); 43 | mList = new ArrayList<>(5); 44 | for (int i = 0; i < 5; i++) { 45 | mList.add(new ReBoundFragment()); 46 | } 47 | mResetDistance = (int) (Util.getScreenWidth(this) / 3f); 48 | mAdapter = new VPAdapter(getSupportFragmentManager(), mList); 49 | viewpager.setAdapter(mAdapter); 50 | viewpager.setPageMargin(30); 51 | reBoundLayout.setNeedReset(false); 52 | reBoundLayout.setResetDistance(mResetDistance); 53 | reBoundLayout.setOnBounceDistanceChangeListener(this); 54 | showTipDistance = Util.dipTopx(this, 50); 55 | } 56 | 57 | @Override 58 | public void finish() { 59 | super.finish(); 60 | } 61 | 62 | @Override 63 | public void onDistanceChange(int distance, int direction) { 64 | switch (direction) { 65 | case DIRECTION_LEFT: 66 | if (distance > showTipDistance) { 67 | rightTip.setVisibility(View.VISIBLE); 68 | rightTip.setTranslationX(showTipDistance - distance); 69 | if (distance > mResetDistance) { 70 | rightTip.setText("松手关闭"); 71 | } else { 72 | rightTip.setText("继续左滑关闭"); 73 | } 74 | } else { 75 | rightTip.setVisibility(View.GONE); 76 | } 77 | break; 78 | case DIRECTION_RIGHT: 79 | if (distance > showTipDistance) { 80 | leftTip.setVisibility(View.VISIBLE); 81 | leftTip.setTranslationX(distance - showTipDistance); 82 | if (distance > mResetDistance) { 83 | leftTip.setText("松手关闭"); 84 | } else { 85 | leftTip.setText("继续右滑关闭"); 86 | } 87 | } else { 88 | leftTip.setVisibility(View.GONE); 89 | } 90 | break; 91 | case DIRECTION_UP: 92 | break; 93 | case DIRECTION_DOWN: 94 | if (distance > showTipDistance) { 95 | topTip.setVisibility(View.VISIBLE); 96 | topTip.setTranslationY(distance - showTipDistance); 97 | if (distance > mResetDistance) { 98 | topTip.setText("松手关闭"); 99 | } else { 100 | topTip.setText("继续下拉关闭,松手左右滑切换"); 101 | } 102 | } else { 103 | topTip.setVisibility(View.GONE); 104 | } 105 | break; 106 | default: 107 | break; 108 | } 109 | } 110 | 111 | @Override 112 | public void onFingerUp(int distance, int direction) { 113 | switch (direction) { 114 | case DIRECTION_LEFT: 115 | if (distance > mResetDistance) { 116 | viewpager.animate().translationXBy(-300).setDuration(50).setInterpolator(new AccelerateInterpolator()); 117 | viewpager.postDelayed(new Runnable() { 118 | @Override 119 | public void run() { 120 | finish(); 121 | overridePendingTransition(0, R.anim.animate_alpha_1_0); 122 | } 123 | }, 50); 124 | } else { 125 | rightTip.setTranslationX(0); 126 | rightTip.setText("继续左滑关闭"); 127 | rightTip.setVisibility(View.GONE); 128 | } 129 | break; 130 | case DIRECTION_RIGHT: 131 | if (distance > mResetDistance) { 132 | viewpager.animate().translationXBy(300).setDuration(50).setInterpolator(new AccelerateInterpolator()); 133 | viewpager.postDelayed(new Runnable() { 134 | @Override 135 | public void run() { 136 | finish(); 137 | overridePendingTransition(0, R.anim.animate_alpha_1_0); 138 | } 139 | }, 50); 140 | } else { 141 | leftTip.setTranslationX(0); 142 | leftTip.setText("继续右滑关闭"); 143 | leftTip.setVisibility(View.GONE); 144 | } 145 | break; 146 | case DIRECTION_DOWN: 147 | if (distance > mResetDistance) { 148 | viewpager.animate().translationYBy(300).setDuration(50).setInterpolator(new AccelerateInterpolator()); 149 | viewpager.postDelayed(new Runnable() { 150 | @Override 151 | public void run() { 152 | finish(); 153 | overridePendingTransition(0, R.anim.animate_alpha_1_0); 154 | } 155 | }, 50); 156 | } else { 157 | topTip.setTranslationX(0); 158 | topTip.setText("继续下拉关闭,松手左右滑切换"); 159 | topTip.setVisibility(View.GONE); 160 | } 161 | break; 162 | default: 163 | break; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/rebound/ReBoundFragment.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.rebound; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import com.zzh12138.reboundlayout.Bean; 15 | import com.zzh12138.reboundlayout.Data; 16 | import com.zzh12138.reboundlayout.R; 17 | import com.zzh12138.reboundlayout.RVAdapter; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * Created by zhangzhihao on 2019/1/8 16:09. 24 | */ 25 | public class ReBoundFragment extends Fragment implements OnBounceDistanceChangeListener { 26 | private View mView; 27 | private RecyclerView mRecycler; 28 | private List mList; 29 | private RVAdapter mAdapter; 30 | private ReBoundLayout mLayout; 31 | private OnBounceDistanceChangeListener onBounceDistanceChangeListener; 32 | 33 | @Override 34 | public void onAttach(Context context) { 35 | super.onAttach(context); 36 | if (context instanceof ReBoundActivity) { 37 | onBounceDistanceChangeListener = (OnBounceDistanceChangeListener) context; 38 | } 39 | } 40 | 41 | @Nullable 42 | @Override 43 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 44 | if (mView == null) { 45 | mView = inflater.inflate(R.layout.fragment_rebound, container, false); 46 | mRecycler = mView.findViewById(R.id.recycler); 47 | mLayout = mView.findViewById(R.id.layout); 48 | mList = new ArrayList<>(10); 49 | for (int i = 0; i < 10; i++) { 50 | mList.add(new Bean(Data.title[i] + i, Data.image[i])); 51 | } 52 | mAdapter = new RVAdapter(mList, getContext()); 53 | mRecycler.setAdapter(mAdapter); 54 | mRecycler.setLayoutManager(new LinearLayoutManager(getContext())); 55 | mLayout.setOnBounceDistanceChangeListener(this); 56 | } else { 57 | ViewGroup parent = (ViewGroup) mView.getParent(); 58 | if (parent != null) { 59 | parent.removeView(mView); 60 | } 61 | } 62 | return mView; 63 | } 64 | 65 | @Override 66 | public void onDistanceChange(int distance, int direction) { 67 | if (onBounceDistanceChangeListener != null) { 68 | onBounceDistanceChangeListener.onDistanceChange(distance, direction); 69 | } 70 | } 71 | 72 | @Override 73 | public void onFingerUp(int distance, int direction) { 74 | if (onBounceDistanceChangeListener != null) { 75 | onBounceDistanceChangeListener.onFingerUp(distance, direction); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/rebound/ReBoundLayout.java: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.rebound; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.util.AttributeSet; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewConfiguration; 11 | import android.view.ViewParent; 12 | import android.view.animation.AccelerateDecelerateInterpolator; 13 | import android.view.animation.Interpolator; 14 | import android.widget.FrameLayout; 15 | import android.widget.LinearLayout; 16 | 17 | import com.zzh12138.reboundlayout.R; 18 | 19 | /** 20 | * Created by zhangzhihao on 2019/1/8 11:04. 21 | */ 22 | public class ReBoundLayout extends FrameLayout { 23 | private static final String TAG = "ReBoundLayout"; 24 | 25 | private int mTouchSlop; 26 | private int mDownX; 27 | private int mDownY; 28 | private boolean isIntercept; 29 | private View innerView; 30 | private float resistance; 31 | private int orientation; 32 | private long mDuration; 33 | private Interpolator mInterpolator; 34 | private boolean isNeedReset; 35 | private int resetDistance; 36 | private OnBounceDistanceChangeListener onBounceDistanceChangeListener; 37 | 38 | 39 | public ReBoundLayout(@NonNull Context context) { 40 | this(context, null); 41 | } 42 | 43 | public ReBoundLayout(@NonNull Context context, @Nullable AttributeSet attrs) { 44 | this(context, attrs, 0); 45 | } 46 | 47 | public ReBoundLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 48 | super(context, attrs, defStyleAttr); 49 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ReBoundLayout); 50 | orientation = array.getInt(R.styleable.ReBoundLayout_reBoundOrientation, LinearLayout.HORIZONTAL); 51 | resistance = array.getFloat(R.styleable.ReBoundLayout_resistance, 3f); 52 | mDuration = array.getInteger(R.styleable.ReBoundLayout_reBoundDuration, 300); 53 | if (resistance < 1) { 54 | resistance = 1f; 55 | } 56 | array.recycle(); 57 | mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 58 | mInterpolator = new AccelerateDecelerateInterpolator(); 59 | resetDistance = Integer.MAX_VALUE; 60 | } 61 | 62 | @Override 63 | public boolean onInterceptTouchEvent(MotionEvent ev) { 64 | switch (ev.getActionMasked()) { 65 | case MotionEvent.ACTION_DOWN: 66 | if (innerView != null) { 67 | innerView.clearAnimation(); 68 | } 69 | mDownX = (int) ev.getX(); 70 | mDownY = (int) ev.getY(); 71 | break; 72 | case MotionEvent.ACTION_MOVE: 73 | int difX = (int) (ev.getX() - mDownX); 74 | int difY = (int) (ev.getY() - mDownY); 75 | if (orientation == LinearLayout.HORIZONTAL) { 76 | if (Math.abs(difX) > mTouchSlop && Math.abs(difX) > Math.abs(difY)) { 77 | ViewParent parent = getParent(); 78 | while (parent != null) { 79 | parent.requestDisallowInterceptTouchEvent(true); 80 | parent = parent.getParent(); 81 | isIntercept = true; 82 | } 83 | if (!innerView.canScrollHorizontally(-1) && difX > 0) { 84 | //右拉到边界 85 | return true; 86 | } 87 | if (!innerView.canScrollHorizontally(1) && difX < 0) { 88 | //左拉到边界 89 | return true; 90 | } 91 | } 92 | } else { 93 | if (Math.abs(difY) > mTouchSlop && Math.abs(difY) > Math.abs(difX)) { 94 | ViewParent parent = getParent(); 95 | while (parent != null) { 96 | parent.requestDisallowInterceptTouchEvent(true); 97 | parent = parent.getParent(); 98 | isIntercept = true; 99 | } 100 | if (!innerView.canScrollVertically(-1) && difY > 0) { 101 | //下拉到边界 102 | return true; 103 | } 104 | if (!innerView.canScrollVertically(1) && difY < 0) { 105 | //上拉到边界 106 | return true; 107 | } 108 | } 109 | } 110 | break; 111 | case MotionEvent.ACTION_UP: 112 | case MotionEvent.ACTION_CANCEL: 113 | if (isIntercept) { 114 | ViewParent parent = getParent(); 115 | while (parent != null) { 116 | parent.requestDisallowInterceptTouchEvent(false); 117 | parent = parent.getParent(); 118 | } 119 | } 120 | isIntercept = false; 121 | mDownX = 0; 122 | mDownY = 0; 123 | break; 124 | default: 125 | break; 126 | } 127 | return super.onInterceptTouchEvent(ev); 128 | } 129 | 130 | @Override 131 | public boolean onTouchEvent(MotionEvent event) { 132 | switch (event.getActionMasked()) { 133 | case MotionEvent.ACTION_MOVE: 134 | if (orientation == LinearLayout.HORIZONTAL) { 135 | int difX = (int) ((event.getX() - mDownX) / resistance); 136 | boolean isRebound = false; 137 | if (!innerView.canScrollHorizontally(-1) && difX > 0) { 138 | //右拉到边界 139 | isRebound = true; 140 | } else if (!innerView.canScrollHorizontally(1) && difX < 0) { 141 | //左拉到边界 142 | isRebound = true; 143 | } 144 | if (isRebound) { 145 | innerView.setTranslationX(difX); 146 | if (onBounceDistanceChangeListener != null) { 147 | onBounceDistanceChangeListener.onDistanceChange(Math.abs(difX), difX > 0 ? 148 | OnBounceDistanceChangeListener.DIRECTION_RIGHT : OnBounceDistanceChangeListener.DIRECTION_LEFT); 149 | } 150 | return true; 151 | } 152 | } else { 153 | int difY = (int) ((event.getY() - mDownY) / resistance); 154 | boolean isRebound = false; 155 | if (!innerView.canScrollVertically(-1) && difY > 0) { 156 | //下拉到边界 157 | isRebound = true; 158 | } else if (!innerView.canScrollVertically(1) && difY < 0) { 159 | //上拉到边界 160 | isRebound = true; 161 | } 162 | if (isRebound) { 163 | innerView.setTranslationY(difY); 164 | if (onBounceDistanceChangeListener != null) { 165 | onBounceDistanceChangeListener.onDistanceChange(Math.abs(difY), difY > 0 ? 166 | OnBounceDistanceChangeListener.DIRECTION_DOWN : OnBounceDistanceChangeListener.DIRECTION_UP); 167 | } 168 | return true; 169 | } 170 | } 171 | break; 172 | case MotionEvent.ACTION_CANCEL: 173 | case MotionEvent.ACTION_UP: 174 | if (orientation == LinearLayout.HORIZONTAL) { 175 | int difX = (int) innerView.getTranslationX(); 176 | if (difX != 0) { 177 | if (Math.abs(difX) <= resetDistance || isNeedReset) { 178 | innerView.animate().translationX(0).setDuration(mDuration).setInterpolator(mInterpolator); 179 | } 180 | if (onBounceDistanceChangeListener != null) { 181 | onBounceDistanceChangeListener.onFingerUp(Math.abs(difX), difX > 0 ? 182 | OnBounceDistanceChangeListener.DIRECTION_RIGHT : OnBounceDistanceChangeListener.DIRECTION_LEFT); 183 | } 184 | } 185 | } else { 186 | int difY = (int) innerView.getTranslationY(); 187 | if (difY != 0) { 188 | if (Math.abs(difY) <= resetDistance || isNeedReset) { 189 | innerView.animate().translationY(0).setDuration(mDuration).setInterpolator(mInterpolator); 190 | } 191 | if (onBounceDistanceChangeListener != null) { 192 | onBounceDistanceChangeListener.onFingerUp(Math.abs(difY), difY > 0 ? 193 | OnBounceDistanceChangeListener.DIRECTION_DOWN : OnBounceDistanceChangeListener.DIRECTION_UP); 194 | } 195 | } 196 | } 197 | break; 198 | default: 199 | break; 200 | } 201 | return super.onTouchEvent(event); 202 | } 203 | 204 | @Override 205 | protected void onFinishInflate() { 206 | super.onFinishInflate(); 207 | if (getChildCount() > 0) { 208 | innerView = getChildAt(0); 209 | } else { 210 | throw new IllegalArgumentException("it must have innerView"); 211 | } 212 | } 213 | 214 | public void seInnerView(View innerView) { 215 | this.innerView = innerView; 216 | } 217 | 218 | public void setResistance(float resistance) { 219 | this.resistance = resistance; 220 | } 221 | 222 | public void setOrientation(int orientation) { 223 | this.orientation = orientation; 224 | } 225 | 226 | public void setDuration(long mDuration) { 227 | this.mDuration = mDuration; 228 | } 229 | 230 | public void setInterpolator(Interpolator mInterpolator) { 231 | this.mInterpolator = mInterpolator; 232 | } 233 | 234 | public void setNeedReset(boolean needReset) { 235 | isNeedReset = needReset; 236 | } 237 | 238 | public void setResetDistance(int resetDistance) { 239 | this.resetDistance = resetDistance; 240 | } 241 | 242 | public void setOnBounceDistanceChangeListener(OnBounceDistanceChangeListener onBounceDistanceChangeListener) { 243 | this.onBounceDistanceChangeListener = onBounceDistanceChangeListener; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /app/src/main/java/com/zzh12138/reboundlayout/rebound/ReBoundLayoutKt.kt: -------------------------------------------------------------------------------- 1 | package com.zzh12138.reboundlayout.rebound 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | import android.view.View 7 | import android.view.ViewConfiguration 8 | import android.view.animation.AccelerateDecelerateInterpolator 9 | import android.widget.FrameLayout 10 | import android.widget.LinearLayout 11 | import com.zzh12138.reboundlayout.R 12 | import com.zzh12138.reboundlayout.rebound.OnBounceDistanceChangeListener.* 13 | import java.lang.IllegalArgumentException 14 | 15 | /** 16 | * Created by zhangzhihao on 2019/1/12 16:53. 17 | */ 18 | class ReBoundLayoutKt @JvmOverloads constructor( 19 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 20 | ) : FrameLayout(context, attrs, defStyleAttr) { 21 | var mTouchSlop = 0 22 | var mDownX = 0 23 | var mDownY = 0 24 | var isIntercept = false 25 | var innerView: View? = null 26 | var resistance = 1f 27 | var orientation = LinearLayout.HORIZONTAL 28 | var mDuration = 0L 29 | var mInterpolator = AccelerateDecelerateInterpolator() 30 | var isNeedReset = false 31 | var resetDistance = Int.MAX_VALUE 32 | var onBounceDistanceChangeListener: OnBounceDistanceChangeListener? = null 33 | 34 | init { 35 | val array = context.obtainStyledAttributes(attrs, R.styleable.ReBoundLayoutKt) 36 | orientation = array.getInt(R.styleable.ReBoundLayoutKt_reBoundOrientationKt, LinearLayout.HORIZONTAL) 37 | resistance = array.getFloat(R.styleable.ReBoundLayoutKt_resistanceKt, 1f) 38 | mDuration = array.getInt(R.styleable.ReBoundLayoutKt_reBoundOrientationKt, 300).toLong() 39 | array.recycle() 40 | if (resistance < 1f) { 41 | resistance = 1f 42 | } 43 | mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop 44 | } 45 | 46 | override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { 47 | innerView?.let { 48 | when (ev?.actionMasked) { 49 | MotionEvent.ACTION_DOWN -> { 50 | it.clearAnimation() 51 | mDownX = ev.x.toInt() 52 | mDownY = ev.x.toInt() 53 | } 54 | MotionEvent.ACTION_MOVE -> { 55 | val difX = ev.x - mDownX 56 | val difY = ev.y - mDownY 57 | if (orientation == LinearLayout.HORIZONTAL) { 58 | if (Math.abs(difX) > mTouchSlop && Math.abs(difX) > Math.abs(difY)) { 59 | var parent = parent 60 | while (parent != null) { 61 | parent.requestDisallowInterceptTouchEvent(true) 62 | parent = parent.parent 63 | isIntercept = true 64 | } 65 | if (!it.canScrollHorizontally(-1) && difX > 0) { 66 | //右啦到边界 67 | return true 68 | } 69 | if (!it.canScrollHorizontally(1) && difX < 0) { 70 | //左拉到边界 71 | return true 72 | } 73 | } 74 | } else { 75 | if (Math.abs(difY) > mTouchSlop && Math.abs(difY) > Math.abs(difX)) { 76 | var parent = parent 77 | while (parent != null) { 78 | parent.requestDisallowInterceptTouchEvent(true) 79 | parent = parent.parent 80 | isIntercept = true 81 | } 82 | if (!it.canScrollVertically(-1) && difY > 0) { 83 | //下拉到边界 84 | return true 85 | } 86 | if (!it.canScrollVertically(1) && difY > 0) { 87 | //上啦到边界 88 | return true 89 | } 90 | } 91 | } 92 | } 93 | MotionEvent.ACTION_UP, 94 | MotionEvent.ACTION_CANCEL -> { 95 | if (isIntercept) { 96 | var parent = parent 97 | while (parent != null) { 98 | parent.requestDisallowInterceptTouchEvent(false) 99 | parent = parent.parent 100 | } 101 | } 102 | isIntercept = false 103 | mDownY = 0 104 | mDownX = 0 105 | } 106 | } 107 | } 108 | return super.onInterceptTouchEvent(ev) 109 | } 110 | 111 | override fun onTouchEvent(event: MotionEvent?): Boolean { 112 | innerView?.let { 113 | when (event?.actionMasked) { 114 | MotionEvent.ACTION_MOVE -> { 115 | if (orientation == LinearLayout.HORIZONTAL) { 116 | val difX = event.x - mDownX / resistance 117 | var isRebound = false 118 | if (!it.canScrollHorizontally(-1) && difX > 0) { 119 | //右啦到边界 120 | isRebound = true 121 | } else if (!it.canScrollHorizontally(1) && difX < 0) { 122 | //左拉到边界 123 | isRebound = true 124 | } 125 | if (isRebound) { 126 | it.translationX = difX 127 | onBounceDistanceChangeListener?.onDistanceChange(Math.abs(difX).toInt(), 128 | if (difX > 0) DIRECTION_RIGHT else DIRECTION_LEFT) 129 | return true 130 | } 131 | } else { 132 | val difY = event.x - mDownY 133 | var isRebound = false 134 | if (!it.canScrollVertically(-1) && difY > 0) { 135 | //下拉到边界 136 | isRebound = true 137 | } else if (!it.canScrollVertically(1) && difY < 0) { 138 | isRebound = true 139 | } 140 | if (isRebound) { 141 | it.translationY = difY 142 | onBounceDistanceChangeListener?.onDistanceChange(Math.abs(difY).toInt(), 143 | if (difY > 0) DIRECTION_DOWN else DIRECTION_UP) 144 | return true 145 | } 146 | } 147 | } 148 | MotionEvent.ACTION_CANCEL, 149 | MotionEvent.ACTION_UP -> { 150 | if (orientation == LinearLayout.HORIZONTAL) { 151 | val difX = it.translationX 152 | if (difX != 0f) { 153 | if (Math.abs(difX) < resetDistance || isNeedReset) { 154 | it.animate().translationX(0f).setDuration(mDuration).interpolator = mInterpolator 155 | } 156 | onBounceDistanceChangeListener?.onFingerUp(Math.abs(difX).toInt(), 157 | if (difX > 0) DIRECTION_RIGHT else DIRECTION_LEFT) 158 | } 159 | } else { 160 | val difY = it.translationY 161 | if(difY!=0f){ 162 | if(Math.abs(difY)0) DIRECTION_DOWN else DIRECTION_UP) 167 | } 168 | } 169 | } 170 | } 171 | } 172 | return super.onTouchEvent(event) 173 | } 174 | 175 | override fun onFinishInflate() { 176 | super.onFinishInflate() 177 | if(childCount>0){ 178 | innerView=getChildAt(0) 179 | }else{ 180 | throw IllegalArgumentException("it must have innerView") 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/animate_alpha_0_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/animate_alpha_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_corner.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/item_decoration.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_circle_image_acitivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_drag.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 |