├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── dictionaries │ └── andy.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jennifer │ │ └── andy │ │ └── nestedscrollingdemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jennifer │ │ │ └── andy │ │ │ └── nestedscrollingdemo │ │ │ ├── MainActivity.java │ │ │ ├── adapter │ │ │ ├── BaseFragmentItemAdapter.java │ │ │ ├── BaseRecyclerViewAdapter.java │ │ │ ├── RecyclerViewHolder.java │ │ │ └── SimpleStringAdapter.java │ │ │ ├── ui │ │ │ ├── TabFragment.java │ │ │ ├── abl │ │ │ │ ├── CdlWithAppBarActivity.java │ │ │ │ └── CdlWithAppBarWithCollActivity.java │ │ │ ├── cdl │ │ │ │ ├── CoordinatorLayoutActivity.java │ │ │ │ ├── CoordinatorLayoutDemo1Activity.java │ │ │ │ ├── CoordinatorLayoutDemo2Activity.java │ │ │ │ ├── CoordinatorLayoutDemo3Activity.java │ │ │ │ ├── CoordinatorLayoutDemo4Activity.java │ │ │ │ └── behavior │ │ │ │ │ ├── BrotherChameleonBehavior.java │ │ │ │ │ ├── BrotherFollowBehavior.java │ │ │ │ │ ├── BrotherFollowWithoutDependsBehavior.java │ │ │ │ │ ├── HeaderScrollingViewBehavior.java │ │ │ │ │ ├── MeasureLayoutBehavior.java │ │ │ │ │ ├── NestedHeaderBehavior.java │ │ │ │ │ └── ScrollingViewBehavior.java │ │ │ └── nested │ │ │ │ ├── NestedScrolling2DemoActivity.java │ │ │ │ ├── NestedScrollingParent2Activity.java │ │ │ │ ├── NestedScrollingParentActivity.java │ │ │ │ ├── NestedTraditionActivity.java │ │ │ │ └── normal_form │ │ │ │ ├── NestedScrollingChild2View.java │ │ │ │ ├── NestedScrollingChildView.java │ │ │ │ ├── NestedScrollingParent2View.java │ │ │ │ └── NestedScrollingParentView.java │ │ │ └── view │ │ │ ├── DependedView.java │ │ │ ├── NestedScrollingParent2Layout.java │ │ │ ├── NestedScrollingParentLayout.java │ │ │ ├── NestedTraditionLayout.java │ │ │ └── StickyNavLayout.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_action_back_black.png │ │ ├── ic_action_back_white.png │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_cdl_abl_ctl.xml │ │ ├── activity_cdl_demo1.xml │ │ ├── activity_cdl_demo2.xml │ │ ├── activity_cdl_demo3.xml │ │ ├── activity_cdl_demo4.xml │ │ ├── activity_cdl_with_appbar.xml │ │ ├── activity_coord_main.xml │ │ ├── activity_main.xml │ │ ├── activity_nested_scrolling2_demo.xml │ │ ├── activity_nested_srolling_parent.xml │ │ ├── activity_nested_srolling_parent2.xml │ │ ├── activity_nested_tradition.xml │ │ ├── fragment_tab.xml │ │ ├── item_single_text.xml │ │ └── layout_common_toolbar.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 │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jennifer │ └── andy │ └── nestedscrollingdemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndyJennifer/NestedScrollingDemo/ebbc2b21417f4177c4d71c3f793ebcd129d83f3c/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/dictionaries/andy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NestedScrollingDemo 2 | 3 | NestedScrollingDemo 是一款帮助理解 Android NestedScrolling 机制的最佳实战项目。通过学习该项目你可以了解: 4 | 5 | - 传统事件分发机制实现嵌套滑动的局限性。 6 | - 谷歌 NestedScrolling 与 NesetdScrolling2 机制的原理实现。 7 | - NestedScrollingChild 与 NestedScrollingParent 实战。 8 | - NestedScrollingChild2 与 NestedScrollingParent2 实战。 9 | - CoordinatorLayout 与 Behavior 实战。 10 | - CoordinatorLayout 与 AppBarLayout 配合使用例子。 11 | - CoordinatorLayout 与 AppBarLayout 、CollapsingToolbarLayout 三者配合使用例子。 12 | 13 | ## 项目中展示的例子 14 | 15 | ### 传统事件分发机制处理嵌套滑动 🐣 16 | 17 | 18 | 19 | 相关类: 20 | 21 | - [NestedTraditionActivity](https://github.com/AndyJennifer/NestedScrollingDemo/blob/master/app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/nested/NestedTraditionActivity.java) 22 | - [NestedTraditionLayout](https://github.com/AndyJennifer/NestedScrollingDemo/blob/master/app/src/main/java/com/jennifer/andy/nestedscrollingdemo/view/NestedTraditionLayout.java) 23 | 24 | ### NestedScrolling 与 NestedScrolling2 机制 🐘 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 41 | 44 | 45 | 46 | 47 | 60 | 73 | 83 | 84 |
NestedScrolling 机制 NestedScrolling2 机制 NestedScrolling2 机制实战例子
36 | 37 | 39 | 40 | 42 | 43 |
48 | 59 | 61 | 72 | 74 | 82 |
85 | 86 | ### CoordinatorLayout 与 Behavior、AppBarLayout、CollapsingToolbarLayout 🐠 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 98 | 101 | 104 | 105 | 106 | 122 | 132 | 145 | 146 |
自定义 Behavior 事件拦截与处理自定义 Behavior 测量与布局Behavior 嵌套滑动交互效果
96 | 97 | 99 | 100 | 102 | 103 |
107 | 123 | 131 | 133 | 144 |
147 | 148 | ### CoordinatorLayout 与 AppBarLayout、CollapsingToolbarLayout 🎉 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 159 | 162 | 163 | 164 | 171 | 178 | 179 |
CoordinatorLayout 与 AppBarLayout 结合使用CoordinatorLayout 与 AppBarLayout、CollapsingToolbarLayout 结合使用
157 | 158 | 160 | 161 |
165 | 170 | 172 | 177 |
180 | 181 | ## 了解更多 182 | 183 | 对项目中的代码有疑惑,可以查看文章: 184 | 185 | - [自定义View事件之进阶篇(一)-NestedScrolling(嵌套滑动)机制](https://juejin.im/post/5d3e639e51882508dc163606) 186 | - [自定义View事件篇进阶篇(二)-自定义NestedScrolling实战](https://juejin.im/post/5d3e860be51d454f6f16ecf0) 187 | - [自定义View事件篇进阶篇(三)-CoordinatorLayout与Behavior](https://juejin.im/post/5d430c5a6fb9a06aeb109d56) 188 | - [自定义View事件之进阶篇(四)-自定义Behavior实战](https://juejin.im/post/5d43be5af265da03c81501a1) 189 | 190 | ## 最后 191 | 192 | 如果你觉得项目不错,欢迎点击 star ❤️或 follow,也可以帮忙分享给你更多的朋友。你的支持与鼓励是给我继续做好该项目的最大动力。 193 | 194 | ## 联系我 195 | 196 | - QQ:443696320 197 | - 简书:[AndyandJennifer](https://www.jianshu.com/users/921c778fb5e1/timeline) 198 | - 掘金:[AndyandJennifer](https://juejin.im/user/5acc1ea06fb9a028bc2e0fc1) 199 | - Email: [andyjennifer@126.com](andyjennifer@126.com) 200 | 201 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.jennifer.andy.nestedscrollingdemo" 7 | minSdkVersion 19 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:27.1.1' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | implementation 'com.android.support:design:27.1.1' 29 | } 30 | -------------------------------------------------------------------------------- /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/jennifer/andy/nestedscrollingdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo; 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.jennifer.andy.nestedscrollingdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 30 | 31 | 33 | 34 | 36 | 37 | 38 | 39 | 42 | 43 | 45 | 46 | 48 | 49 | 51 | 52 | 54 | 55 | 56 | 58 | 59 | 60 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | import com.jennifer.andy.nestedscrollingdemo.ui.abl.CdlWithAppBarActivity; 9 | import com.jennifer.andy.nestedscrollingdemo.ui.abl.CdlWithAppBarWithCollActivity; 10 | import com.jennifer.andy.nestedscrollingdemo.ui.cdl.CoordinatorLayoutActivity; 11 | import com.jennifer.andy.nestedscrollingdemo.ui.nested.NestedScrolling2DemoActivity; 12 | import com.jennifer.andy.nestedscrollingdemo.ui.nested.NestedScrollingParent2Activity; 13 | import com.jennifer.andy.nestedscrollingdemo.ui.nested.NestedScrollingParentActivity; 14 | import com.jennifer.andy.nestedscrollingdemo.ui.nested.NestedTraditionActivity; 15 | 16 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 17 | 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | findViewAndSetListener(); 24 | } 25 | 26 | private void findViewAndSetListener() { 27 | findViewById(R.id.btn_nested_scrolling_tradition).setOnClickListener(this); 28 | findViewById(R.id.btn_nested_scrolling).setOnClickListener(this); 29 | findViewById(R.id.btn_nested_scrolling2).setOnClickListener(this); 30 | findViewById(R.id.btn_nested_scrolling2Demo).setOnClickListener(this); 31 | findViewById(R.id.btn_coordinator_layout).setOnClickListener(this); 32 | findViewById(R.id.btn_coor_with_appbar).setOnClickListener(this); 33 | findViewById(R.id.btn_coor_with_appbar_with_coll).setOnClickListener(this); 34 | 35 | } 36 | 37 | @Override 38 | public void onClick(View view) { 39 | switch (view.getId()) { 40 | case R.id.btn_nested_scrolling_tradition://传统嵌套滑动 41 | startActivity(new Intent(this, NestedTraditionActivity.class)); 42 | break; 43 | case R.id.btn_nested_scrolling://实现NestedScrollingParent机制的嵌套滑动 44 | startActivity(new Intent(this, NestedScrollingParentActivity.class)); 45 | break; 46 | case R.id.btn_nested_scrolling2://实现NestedScrollingParent2机制的嵌套滑动 47 | startActivity(new Intent(this, NestedScrollingParent2Activity.class)); 48 | break; 49 | case R.id.btn_nested_scrolling2Demo://嵌套滑动实际使用例子 50 | startActivity(new Intent(this, NestedScrolling2DemoActivity.class)); 51 | break; 52 | case R.id.btn_coordinator_layout://CoordinatorLayout效果展示 53 | startActivity(new Intent(this, CoordinatorLayoutActivity.class)); 54 | break; 55 | case R.id.btn_coor_with_appbar://CoordinatorLayout与AppBarLayout结合使用 56 | startActivity(new Intent(this, CdlWithAppBarActivity.class)); 57 | break; 58 | case R.id.btn_coor_with_appbar_with_coll://CoordinatorLayout与AppBarLayout与CollapsingToolbarLayout 三者结合使用 59 | startActivity(new Intent(this, CdlWithAppBarWithCollActivity.class)); 60 | break; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/adapter/BaseFragmentItemAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.adapter; 2 | 3 | import android.support.annotation.Nullable; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentPagerAdapter; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Author: andy.xwt 12 | * Date: 2018/8/8 15:26 13 | * Description: 14 | */ 15 | 16 | public class BaseFragmentItemAdapter extends FragmentPagerAdapter { 17 | 18 | private List mFragments; 19 | private List mTitles; 20 | 21 | public BaseFragmentItemAdapter(FragmentManager fm, List fragments, List titles) { 22 | super(fm); 23 | this.mFragments = fragments; 24 | this.mTitles = titles; 25 | } 26 | 27 | @Override 28 | public Fragment getItem(int position) { 29 | return mFragments.get(position); 30 | } 31 | 32 | @Override 33 | public int getCount() { 34 | return mFragments.size(); 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public CharSequence getPageTitle(int position) { 40 | return mTitles.get(position); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/adapter/BaseRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.IdRes; 5 | import android.support.annotation.IntRange; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * Author: andy.xwt 16 | * Date: 2017/5/17 17:14 17 | * Description: 基础 RecyclerView适配器 18 | */ 19 | 20 | public abstract class BaseRecyclerViewAdapter extends RecyclerView.Adapter { 21 | 22 | protected int mLayoutId; 23 | protected List mData; 24 | protected Context mContext; 25 | protected View mRootView;// 26 | protected List mHolders = new ArrayList<>(); 27 | private RecyclerView mRecyclerView; 28 | private onItemClickListener mOnItemClickListener; 29 | private onItemChildClickListener mOnItemChildClickListener; 30 | 31 | public BaseRecyclerViewAdapter(int layoutId, List data, Context context) { 32 | mLayoutId = layoutId; 33 | mData = data; 34 | mContext = context; 35 | } 36 | 37 | public BaseRecyclerViewAdapter(int layoutId, Context context) { 38 | mLayoutId = layoutId; 39 | mContext = context; 40 | } 41 | 42 | @Override 43 | public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 44 | mRootView = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false); 45 | final RecyclerViewHolder holder = new RecyclerViewHolder(mRootView); 46 | mHolders.add(holder); 47 | holder.itemView.setOnClickListener(new View.OnClickListener() { 48 | @Override 49 | public void onClick(View v) { 50 | if (mOnItemClickListener != null) { 51 | mOnItemClickListener.onClick(v, holder.getLayoutPosition()); 52 | } 53 | } 54 | }); 55 | holder.setAdapter(this); 56 | return holder; 57 | } 58 | 59 | @Override 60 | public void onBindViewHolder(final RecyclerViewHolder holder, int position) { 61 | bindData(holder, mData.get(position), position); 62 | 63 | 64 | } 65 | 66 | @Override 67 | public int getItemCount() { 68 | return mData == null ? 0 : mData.size(); 69 | } 70 | 71 | /** 72 | * 绑定数据 73 | * 74 | * @param holder 75 | * @param t 数据源 76 | * @param position 位置 77 | */ 78 | protected abstract void bindData(RecyclerViewHolder holder, T t, int position); 79 | 80 | 81 | public List getData() { 82 | return mData; 83 | } 84 | 85 | public void setData(List data) { 86 | mData = data; 87 | } 88 | 89 | /** 90 | * 添加新数据(集合) 91 | */ 92 | public void addData(List data) { 93 | if (data != null && data.size() > 0) { 94 | mData.addAll(data); 95 | notifyItemRangeInserted(mData.size(), data.size()); 96 | } 97 | } 98 | 99 | /** 100 | * 添加一个数据 101 | */ 102 | public void addData(T data) { 103 | if (data != null) { 104 | mData.add(data); 105 | notifyItemInserted(mData.size()); 106 | } 107 | } 108 | 109 | 110 | /** 111 | * 设置新数据 112 | */ 113 | public void setNewData(List data) { 114 | if (data != null && data.size() > 0) { 115 | mData = data; 116 | notifyDataSetChanged(); 117 | } 118 | } 119 | 120 | /** 121 | * 清空数据 122 | */ 123 | public void clearData() { 124 | mData.clear(); 125 | notifyDataSetChanged(); 126 | } 127 | 128 | /** 129 | * 根据位置删除数据 130 | */ 131 | public void remove(@IntRange(from = 0) int position) { 132 | mData.remove(position); 133 | notifyItemRemoved(position); 134 | } 135 | 136 | private void checkNotNull() { 137 | if (getRecyclerView() == null) { 138 | throw new RuntimeException("please bind recyclerView first!"); 139 | } 140 | } 141 | 142 | /** 143 | * 根据当前位置获取相应item中的view 144 | */ 145 | public View getViewByPosition(int position, @IdRes int viewId) { 146 | checkNotNull(); 147 | return getViewByPosition(getRecyclerView(), position, viewId); 148 | } 149 | 150 | /** 151 | * 获取当前 152 | */ 153 | public V getViewByPosition(RecyclerView recyclerView, int position, @IdRes int viewId) { 154 | if (recyclerView == null) { 155 | return null; 156 | } 157 | RecyclerViewHolder viewHolder = (RecyclerViewHolder) recyclerView.findViewHolderForLayoutPosition(position); 158 | if (viewHolder == null) { 159 | return null; 160 | } 161 | return viewHolder.getView(viewId); 162 | } 163 | 164 | public View getRootView() { 165 | return mRootView; 166 | } 167 | 168 | 169 | protected RecyclerView getRecyclerView() { 170 | return mRecyclerView; 171 | } 172 | 173 | private void setRecyclerView(RecyclerView recyclerView) { 174 | mRecyclerView = recyclerView; 175 | } 176 | 177 | /** 178 | * item 点击事件 179 | */ 180 | public interface onItemClickListener { 181 | void onClick(View view, int position); 182 | } 183 | 184 | public void setOnItemClickListener(onItemClickListener onItemClickListener) { 185 | mOnItemClickListener = onItemClickListener; 186 | } 187 | 188 | 189 | /** 190 | * item 中子布局点击事件 191 | */ 192 | public interface onItemChildClickListener { 193 | void onItemChildClick(View view, int position); 194 | } 195 | 196 | 197 | public onItemChildClickListener getOnItemChildClickListener() { 198 | return mOnItemChildClickListener; 199 | } 200 | 201 | public void setOnItemChildClickListener(onItemChildClickListener onItemChildClickListener) { 202 | mOnItemChildClickListener = onItemChildClickListener; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/adapter/RecyclerViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.adapter; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.support.annotation.ColorInt; 5 | import android.support.annotation.DrawableRes; 6 | import android.support.annotation.IdRes; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.text.SpannableStringBuilder; 10 | import android.util.SparseArray; 11 | import android.view.View; 12 | import android.widget.CheckBox; 13 | import android.widget.ImageView; 14 | import android.widget.TextView; 15 | 16 | import java.util.LinkedHashSet; 17 | 18 | /** 19 | * Author: andy.xwt 20 | * Date: 2017/8/8 11:55 21 | * Description: 22 | */ 23 | 24 | public class RecyclerViewHolder extends RecyclerView.ViewHolder { 25 | 26 | private SparseArray mViews; 27 | private final LinkedHashSet childClickViewIds = new LinkedHashSet(); 28 | private BaseRecyclerViewAdapter adapter; 29 | 30 | public RecyclerViewHolder(View itemView) { 31 | super(itemView); 32 | this.mViews = new SparseArray<>(); 33 | } 34 | 35 | 36 | public V getView(@IdRes int viewId) { 37 | View view = mViews.get(viewId); 38 | if (view == null) { 39 | view = itemView.findViewById(viewId); 40 | mViews.put(viewId, view); 41 | } 42 | return (V) view; 43 | } 44 | 45 | /** 46 | * 设置textView值 47 | * 48 | * @param viewId 49 | * @param text 内容 50 | * @return 51 | */ 52 | public RecyclerViewHolder setText(@IdRes int viewId, String text) { 53 | TextView tv = getView(viewId); 54 | tv.setText(text); 55 | return this; 56 | } 57 | 58 | 59 | /** 60 | * 设置textView值 61 | */ 62 | public RecyclerViewHolder setText(@IdRes int viewId, SpannableStringBuilder text) { 63 | TextView tv = getView(viewId); 64 | tv.setText(text); 65 | return this; 66 | } 67 | 68 | /** 69 | * 设置textVie颜色 70 | */ 71 | public RecyclerViewHolder setTextColor(@IdRes int viewId, @ColorInt int color) { 72 | TextView tv = getView(viewId); 73 | tv.setTextColor(color); 74 | return this; 75 | } 76 | 77 | 78 | 79 | public RecyclerViewHolder setImage(@IdRes int viewId, Drawable drawable) { 80 | ImageView iv = getView(viewId); 81 | iv.setImageDrawable(drawable); 82 | return this; 83 | } 84 | 85 | 86 | public RecyclerViewHolder setImage(@IdRes int viewId, @DrawableRes int resId) { 87 | ImageView iv = getView(viewId); 88 | iv.setImageResource(resId); 89 | return this; 90 | } 91 | 92 | 93 | public RecyclerViewHolder setCompoundDrawables(@IdRes int viewId, 94 | @Nullable Drawable left, 95 | @Nullable Drawable top, 96 | @Nullable Drawable right, 97 | @Nullable Drawable bottom) { 98 | TextView tv = getView(viewId); 99 | tv.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom); 100 | return this; 101 | } 102 | 103 | /** 104 | * 设置背景 105 | * 106 | * @param viewId 107 | * @param resId 108 | * @return 109 | */ 110 | public RecyclerViewHolder setBackgroundResource(@IdRes int viewId, @DrawableRes int resId) { 111 | View view = getView(viewId); 112 | view.setBackgroundResource(resId); 113 | return this; 114 | } 115 | 116 | /** 117 | * 设置image 为null,解决viewHolder复用问题 118 | * 119 | * @param viewId 120 | */ 121 | public RecyclerViewHolder setImageBitMapNull(@IdRes int viewId) { 122 | ImageView iv = getView(viewId); 123 | iv.setImageBitmap(null); 124 | return this; 125 | } 126 | 127 | /** 128 | * 设置是否可见 129 | * 130 | * @param viewId 131 | * @param isVisible 是否可见 132 | * @return 133 | */ 134 | public RecyclerViewHolder setVisible(@IdRes int viewId, boolean isVisible) { 135 | View tv = getView(viewId); 136 | tv.setVisibility(isVisible ? View.VISIBLE : View.GONE); 137 | return this; 138 | } 139 | 140 | 141 | /** 142 | * 设置是否选中 143 | * 144 | * @param viewId 145 | * @param isChecked 是否选中 146 | * @return 147 | */ 148 | public RecyclerViewHolder setChecked(@IdRes int viewId, boolean isChecked) { 149 | CheckBox tv = getView(viewId); 150 | if (tv != null) { 151 | tv.setChecked(isChecked); 152 | } 153 | return this; 154 | } 155 | 156 | 157 | /** 158 | * 设置是否可用 159 | */ 160 | public RecyclerViewHolder setEnabled(@IdRes int viewId, boolean isEnabled) { 161 | View view = getView(viewId); 162 | if (view != null) { 163 | view.setEnabled(isEnabled); 164 | } 165 | return this; 166 | } 167 | 168 | /** 169 | * 设置点击事件 170 | */ 171 | public RecyclerViewHolder setOnClickListener(@IdRes int viewId, View.OnClickListener listener) { 172 | View view = getView(viewId); 173 | view.setOnClickListener(listener); 174 | return this; 175 | } 176 | 177 | /** 178 | * 添加点击事件 注意添加点击事件的时候必须要设置adapter 179 | * 180 | * @see #setAdapter(BaseRecyclerViewAdapter) 181 | */ 182 | public RecyclerViewHolder addOnClickListener(@IdRes int viewId) { 183 | this.childClickViewIds.add(viewId); 184 | View view = this.getView(viewId); 185 | if (view != null) { 186 | if (!view.isClickable()) { 187 | view.setClickable(true); 188 | } 189 | view.setOnClickListener(new View.OnClickListener() { 190 | public void onClick(View v) { 191 | if (RecyclerViewHolder.this.adapter.getOnItemChildClickListener() != null) { 192 | RecyclerViewHolder.this.adapter.getOnItemChildClickListener().onItemChildClick(v, RecyclerViewHolder.this.getLayoutPosition()); 193 | } 194 | } 195 | }); 196 | } 197 | return this; 198 | } 199 | 200 | /** 201 | * 设置适配器 202 | */ 203 | public BaseRecyclerViewAdapter getAdapter() { 204 | return adapter; 205 | } 206 | 207 | public void setAdapter(BaseRecyclerViewAdapter adapter) { 208 | this.adapter = adapter; 209 | } 210 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/adapter/SimpleStringAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.adapter; 2 | 3 | import android.content.Context; 4 | 5 | import com.jennifer.andy.nestedscrollingdemo.R; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Author: andy.xwt 11 | * Date: 2018/8/8 15:55 12 | * Description:简单的一行文本适配器 13 | */ 14 | 15 | public class SimpleStringAdapter extends BaseRecyclerViewAdapter { 16 | 17 | public SimpleStringAdapter(List strs, Context context) { 18 | super(R.layout.item_single_text, strs, context); 19 | } 20 | 21 | @Override 22 | protected void bindData(RecyclerViewHolder holder, String text, int position) { 23 | holder.setText(R.id.tv_text, text); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/TabFragment.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v7.widget.DividerItemDecoration; 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.jennifer.andy.nestedscrollingdemo.R; 15 | import com.jennifer.andy.nestedscrollingdemo.adapter.SimpleStringAdapter; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | /** 21 | * Author: andy.xwt 22 | * Date: 2018/8/8 15:29 23 | * Description: 24 | */ 25 | 26 | public class TabFragment extends Fragment { 27 | 28 | 29 | private RecyclerView mRecyclerView; 30 | private String mText; 31 | 32 | public static TabFragment newInstance(String text) { 33 | Bundle args = new Bundle(); 34 | TabFragment fragment = new TabFragment(); 35 | args.putString("text", text); 36 | fragment.setArguments(args); 37 | return fragment; 38 | } 39 | 40 | @Nullable 41 | @Override 42 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 43 | return inflater.inflate(R.layout.fragment_tab, container, false); 44 | } 45 | 46 | @Override 47 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 48 | super.onViewCreated(view, savedInstanceState); 49 | mText = getArguments().getString("text"); 50 | initView(view); 51 | } 52 | 53 | private void initView(View view) { 54 | mRecyclerView = view.findViewById(R.id.recycler_view); 55 | mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 56 | mRecyclerView.setAdapter(new SimpleStringAdapter(initStrings(), getContext())); 57 | mRecyclerView.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL)); 58 | } 59 | 60 | private List initStrings() { 61 | List list = new ArrayList<>(); 62 | for (int i = 0; i < 100; i++) { 63 | list.add(mText); 64 | } 65 | return list; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/abl/CdlWithAppBarActivity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.abl; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.DividerItemDecoration; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | 9 | import com.jennifer.andy.nestedscrollingdemo.R; 10 | import com.jennifer.andy.nestedscrollingdemo.adapter.SimpleStringAdapter; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Author: andy.xwt 17 | * Date: 2018/8/8 13:56 18 | * Description:coordinatorLayout与AppBarLayout的使用 19 | */ 20 | 21 | public class CdlWithAppBarActivity extends AppCompatActivity { 22 | 23 | private RecyclerView mRecyclerView; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_cdl_with_appbar); 29 | initView(); 30 | } 31 | 32 | private void initView() { 33 | mRecyclerView = findViewById(R.id.recycler_view); 34 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 35 | mRecyclerView.setAdapter(new SimpleStringAdapter(initStrings(), this)); 36 | mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); 37 | } 38 | 39 | private List initStrings() { 40 | List list = new ArrayList<>(); 41 | for (int i = 0; i < 100; i++) { 42 | list.add("简单文本" + i); 43 | } 44 | return list; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/abl/CdlWithAppBarWithCollActivity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.abl; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.DividerItemDecoration; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | 9 | import com.jennifer.andy.nestedscrollingdemo.R; 10 | import com.jennifer.andy.nestedscrollingdemo.adapter.SimpleStringAdapter; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Author: andy.xwt 17 | * Date: 2018/8/8 13:56 18 | * Description: 19 | */ 20 | 21 | public class CdlWithAppBarWithCollActivity extends AppCompatActivity { 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_cdl_abl_ctl); 27 | initView(); 28 | } 29 | 30 | private void initView() { 31 | final RecyclerView recyclerView = findViewById(R.id.recycler_view); 32 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 33 | recyclerView.setAdapter(new SimpleStringAdapter(initStrings(), this)); 34 | recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); 35 | } 36 | 37 | private List initStrings() { 38 | List list = new ArrayList<>(); 39 | for (int i = 0; i < 100; i++) { 40 | list.add(i + "--->CollapsingToolbarLayout与AppBarLayout"); 41 | } 42 | return list; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/CoordinatorLayoutActivity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | import com.jennifer.andy.nestedscrollingdemo.R; 9 | 10 | /** 11 | * Author: andy.xwt 12 | * Date: 2018/8/8 13:56 13 | * Description:CoordinatorLayout的效果展示。 14 | */ 15 | 16 | public class CoordinatorLayoutActivity extends AppCompatActivity implements View.OnClickListener { 17 | 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_coord_main); 23 | initView(); 24 | } 25 | 26 | private void initView() { 27 | findViewById(R.id.btn_demo1).setOnClickListener(this); 28 | findViewById(R.id.btn_demo2).setOnClickListener(this); 29 | findViewById(R.id.btn_demo3).setOnClickListener(this); 30 | findViewById(R.id.btn_demo4).setOnClickListener(this); 31 | findViewById(R.id.btn_demo5).setOnClickListener(this); 32 | 33 | } 34 | 35 | 36 | @Override 37 | public void onClick(View v) { 38 | switch (v.getId()) { 39 | case R.id.btn_demo1://多个view的协同交互效果 40 | startActivity(new Intent(this, CoordinatorLayoutDemo1Activity.class)); 41 | break; 42 | case R.id.btn_demo2://不重写layoutDependsOn方法,而是在布局使用xml中使用layout_anchor来确定依赖关系 43 | startActivity(new Intent(this, CoordinatorLayoutDemo2Activity.class)); 44 | break; 45 | case R.id.btn_demo3://自定义Behavior测量与布局 46 | startActivity(new Intent(this, CoordinatorLayoutDemo3Activity.class)); 47 | break; 48 | case R.id.btn_demo4://Behavior嵌套滑动交互效果 49 | startActivity(new Intent(this, CoordinatorLayoutDemo4Activity.class)); 50 | break; 51 | case R.id.btn_demo5://自定义Behavior事件拦截与处理 52 | startActivity(new Intent(this, CoordinatorLayoutDemo2Activity.class)); 53 | break; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/CoordinatorLayoutDemo1Activity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.jennifer.andy.nestedscrollingdemo.R; 7 | 8 | /** 9 | * Author: andy.xwt 10 | * Date: 2018/8/8 13:56 11 | * Description: 12 | * 没有实现NestedScrollingChild接口下,多个view的交互效果 13 | */ 14 | 15 | public class CoordinatorLayoutDemo1Activity extends AppCompatActivity { 16 | 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_cdl_demo1); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/CoordinatorLayoutDemo2Activity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.jennifer.andy.nestedscrollingdemo.R; 7 | 8 | /** 9 | * Author: andy.xwt 10 | * Date: 2018/8/8 13:56 11 | * Description: 12 | * 不重写layoutDependsOn方法,而是在布局使用xml中使用layout_anchor来确定依赖关系 13 | * 具体为什么可以这样使用,请查看{@link android.support.design.widget.CoordinatorLayout#onChildViewsChanged(int)}方法中 14 | * 15 | * for (int j = 0; j < i; j++) { 16 | * final View checkChild = mDependencySortedChildren.get(j); 17 | * 18 | * if (lp.mAnchorDirectChild == checkChild) {//这里 19 | * offsetChildToAnchor(child, layoutDirection);//与这里 20 | * } 21 | * } 22 | */ 23 | 24 | public class CoordinatorLayoutDemo2Activity extends AppCompatActivity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_cdl_demo2); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/CoordinatorLayoutDemo3Activity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.DividerItemDecoration; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | 9 | import com.jennifer.andy.nestedscrollingdemo.R; 10 | import com.jennifer.andy.nestedscrollingdemo.adapter.SimpleStringAdapter; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Author: andy.xwt 17 | * Date: 2018/8/8 13:56 18 | * Description: 19 | * 自定义Behavior测量与布局 20 | */ 21 | 22 | public class CoordinatorLayoutDemo3Activity extends AppCompatActivity { 23 | 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_cdl_demo3); 29 | initView(); 30 | } 31 | 32 | private void initView() { 33 | final RecyclerView recyclerView = findViewById(R.id.recycler_view); 34 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 35 | recyclerView.setAdapter(new SimpleStringAdapter(initStrings(), this)); 36 | recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); 37 | } 38 | 39 | private List initStrings() { 40 | List list = new ArrayList<>(); 41 | for (int i = 0; i < 100; i++) { 42 | list.add(i + "--->定义Behavior测量与布局"); 43 | } 44 | return list; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/CoordinatorLayoutDemo4Activity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.DividerItemDecoration; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | 9 | import com.jennifer.andy.nestedscrollingdemo.R; 10 | import com.jennifer.andy.nestedscrollingdemo.adapter.SimpleStringAdapter; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Author: andy.xwt 17 | * Date: 2018/8/8 13:56 18 | * Description:Behavior嵌套滑动效果。 19 | * 因为要重新设置Recycler的位置,所以说需要自定义Behavior{@link com.jennifer.andy.nestedscrollingdemo.ui.cdl.behavior.ScrollingViewBehavior} 20 | */ 21 | 22 | public class CoordinatorLayoutDemo4Activity extends AppCompatActivity { 23 | 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_cdl_demo4); 29 | initView(); 30 | } 31 | 32 | 33 | private void initView() { 34 | final RecyclerView recyclerView = findViewById(R.id.recycler_view); 35 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 36 | recyclerView.setAdapter(new SimpleStringAdapter(initStrings(), this)); 37 | recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); 38 | 39 | /** 40 | * 如果你在CoordinatorLayout结合RecyclerView使用了嵌套滑动效果。那么你会发现,当我们使用recyclerView.smoothScrollToPosition(0)时, 41 | * 之前设置的嵌套滑动效果会失效。也就是RecyclerView只会滚动到顶部。而RecyclerView 42 | * 所依赖的控件,它的嵌套滑动是没有出来的。如果你想看一下我的解决方法。可以将R.layout.activity_cdl_demo4布局中的注释解开。 43 | * 并查看NestedHeaderBehavior类中的onStopNestedScroll方法的处理。 44 | */ 45 | // findViewById(R.id.btn_ok).setOnClickListener(new View.OnClickListener() { 46 | // @Override 47 | // public void onClick(View v) { 48 | // recyclerView.startNestedScroll(View.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH); 49 | // recyclerView.smoothScrollToPosition(0); 50 | // } 51 | // }); 52 | } 53 | 54 | private List initStrings() { 55 | List list = new ArrayList<>(); 56 | for (int i = 0; i < 100; i++) { 57 | list.add(i + "--->Behavior的嵌套滑动"); 58 | } 59 | return list; 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/behavior/BrotherChameleonBehavior.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl.behavior; 2 | 3 | import android.animation.ArgbEvaluator; 4 | import android.content.Context; 5 | import android.graphics.Color; 6 | import android.support.design.widget.CoordinatorLayout; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import com.jennifer.andy.nestedscrollingdemo.view.DependedView; 11 | 12 | /** 13 | * Author: andy.xwt 14 | * Date: 2019-07-11 10:53 15 | * Description:变色行为 16 | */ 17 | 18 | public class BrotherChameleonBehavior extends CoordinatorLayout.Behavior { 19 | 20 | private ArgbEvaluator mArgbEvaluator = new ArgbEvaluator(); 21 | 22 | public BrotherChameleonBehavior(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | } 25 | 26 | @Override 27 | public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { 28 | return dependency instanceof DependedView; 29 | } 30 | 31 | @Override 32 | public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { 33 | int color = (int) mArgbEvaluator.evaluate(dependency.getY() / parent.getHeight(), Color.WHITE, Color.BLACK); 34 | child.setBackgroundColor(color); 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/behavior/BrotherFollowBehavior.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl.behavior; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.CoordinatorLayout; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | 8 | import com.jennifer.andy.nestedscrollingdemo.view.DependedView; 9 | 10 | /** 11 | * Author: andy.xwt 12 | * Date: 2019-07-11 10:53 13 | * Description:跟随行为 14 | */ 15 | 16 | public class BrotherFollowBehavior extends CoordinatorLayout.Behavior { 17 | 18 | public BrotherFollowBehavior(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | } 21 | 22 | @Override 23 | public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { 24 | return dependency instanceof DependedView; 25 | } 26 | 27 | @Override 28 | public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { 29 | child.setY(dependency.getBottom() + 50); 30 | child.setX(dependency.getX()); 31 | return true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/behavior/BrotherFollowWithoutDependsBehavior.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl.behavior; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.CoordinatorLayout; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | 8 | /** 9 | * Author: andy.xwt 10 | * Date: 2019-07-11 10:53 11 | * Description: 不重写layoutDependsOn方法,而是在布局使用xml中使用layout_anchor来确定依赖关系 12 | */ 13 | 14 | public class BrotherFollowWithoutDependsBehavior extends CoordinatorLayout.Behavior { 15 | 16 | public BrotherFollowWithoutDependsBehavior(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | } 19 | 20 | @Override 21 | public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { 22 | child.setY(dependency.getTop() - 50);//始终在依赖控件上面50个像素 23 | child.setX(dependency.getX()); 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/behavior/HeaderScrollingViewBehavior.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl.behavior; 2 | 3 | import android.content.Context; 4 | import android.graphics.Rect; 5 | import android.support.design.widget.CoordinatorLayout; 6 | import android.support.v4.view.GravityCompat; 7 | import android.support.v4.view.ViewCompat; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.Gravity; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Author: andy.xwt 18 | * Date: 2019-07-12 15:39 19 | * Description: 20 | * 用于测量对应控件的高度和布局 需要配合{@link NestedHeaderBehavior}使用 21 | */ 22 | public class HeaderScrollingViewBehavior extends CoordinatorLayout.Behavior { 23 | 24 | final Rect mTempRect1 = new Rect(); 25 | final Rect mTempRect2 = new Rect(); 26 | 27 | public static final String TAG = "HeaderScrolling"; 28 | 29 | public HeaderScrollingViewBehavior() { 30 | } 31 | 32 | public HeaderScrollingViewBehavior(Context context, AttributeSet attrs) { 33 | super(context, attrs); 34 | } 35 | 36 | 37 | @Override 38 | public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 39 | 40 | //获取当前滚动控件的测量模式 41 | final int childLpHeight = child.getLayoutParams().height; 42 | 43 | //只有当前滚动控件为match_parent/wrap_content时才重新测量其高度,因为固定高度不会出现底部空白的情况 44 | if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT 45 | || childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { 46 | 47 | //获取当前child依赖的对象集合 48 | final List dependencies = parent.getDependencies(child); 49 | 50 | final View header = findFirstDependency(dependencies); 51 | if (header != null) { 52 | if (ViewCompat.getFitsSystemWindows(header) 53 | && !ViewCompat.getFitsSystemWindows(child)) { 54 | // If the header is fitting system windows then we need to also, 55 | // otherwise we'll get CoL's compatible measuring 56 | ViewCompat.setFitsSystemWindows(child, true); 57 | 58 | if (ViewCompat.getFitsSystemWindows(child)) { 59 | // If the set succeeded, trigger a new layout and return true 60 | child.requestLayout(); 61 | return true; 62 | } 63 | } 64 | //获取当前父控件中可用的距离, 65 | int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec); 66 | if (availableHeight == 0) { 67 | 68 | // If the measure spec doesn't specify a size, use the current height 69 | availableHeight = parent.getHeight(); 70 | } 71 | //计算当前滚动控件的高度。 72 | final int height = availableHeight - header.getMeasuredHeight() + getScrollRange(header); 73 | final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, 74 | childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT 75 | ? View.MeasureSpec.EXACTLY 76 | : View.MeasureSpec.AT_MOST); 77 | 78 | //测量当前滚动的View的正确高度 79 | parent.onMeasureChild(child, parentWidthMeasureSpec, 80 | widthUsed, heightMeasureSpec, heightUsed); 81 | 82 | return true; 83 | } 84 | } 85 | return false; 86 | } 87 | 88 | @Override 89 | public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) { 90 | final List dependencies = parent.getDependencies(child); 91 | final View header = findFirstDependency(dependencies); 92 | 93 | if (header != null) { 94 | final CoordinatorLayout.LayoutParams lp = 95 | (CoordinatorLayout.LayoutParams) child.getLayoutParams(); 96 | final Rect available = mTempRect1; 97 | 98 | //得到依赖控件下方的坐标。 99 | available.set(parent.getPaddingLeft() + lp.leftMargin, 100 | header.getBottom() + lp.topMargin, 101 | parent.getWidth() - parent.getPaddingRight() - lp.rightMargin, 102 | parent.getHeight() + header.getBottom() 103 | - parent.getPaddingBottom() - lp.bottomMargin); 104 | 105 | //拿到上面计算的坐标后,根据当前控件在父控件中设置的gravity,重新计算并得到控件在父控件中的坐标 106 | final Rect out = mTempRect2; 107 | GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(), 108 | child.getMeasuredHeight(), available, out, layoutDirection); 109 | 110 | //拿到坐标后重新布局 111 | child.layout(out.left, out.top, out.right, out.bottom); 112 | 113 | } else { 114 | //如果没有依赖,则调用父控件来处理布局 115 | parent.onLayoutChild(child, layoutDirection); 116 | } 117 | return true; 118 | } 119 | 120 | @Override 121 | public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { 122 | offsetChildAsNeeded(parent, child, dependency); 123 | return true; 124 | } 125 | 126 | private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) { 127 | final CoordinatorLayout.Behavior behavior = 128 | ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior(); 129 | if (behavior instanceof NestedHeaderBehavior) { 130 | Log.i(TAG, "offsetChildAsNeeded: " + dependency.getBottom() + "--->" + child.getTop() + "---->" + ((NestedHeaderBehavior) behavior).getOffset()); 131 | ViewCompat.offsetTopAndBottom(child, dependency.getBottom() - child.getTop() + ((NestedHeaderBehavior) behavior).getOffset()); 132 | } 133 | } 134 | 135 | /** 136 | * 从依赖集合中获取第一个 137 | */ 138 | View findFirstDependency(List views) { 139 | if (views != null && !views.isEmpty()) { 140 | return views.get(0); 141 | } 142 | return null; 143 | } 144 | 145 | /** 146 | * 矫正当前Gravity 147 | */ 148 | private static int resolveGravity(int gravity) { 149 | return gravity == Gravity.NO_GRAVITY ? GravityCompat.START | Gravity.TOP : gravity; 150 | } 151 | 152 | 153 | /** 154 | * 获取当前View的滑动范围,一般情况下,为view的高度。 155 | * 特殊情况下,滚动范围会小于View的高度。这种一般都是折叠布局 156 | * 157 | * @param v 158 | * @return 159 | */ 160 | int getScrollRange(View v) { 161 | return v.getMeasuredHeight(); 162 | } 163 | 164 | 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/behavior/MeasureLayoutBehavior.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl.behavior; 2 | 3 | import android.content.Context; 4 | import android.graphics.Rect; 5 | import android.support.design.widget.CoordinatorLayout; 6 | import android.support.v4.view.GravityCompat; 7 | import android.support.v4.view.ViewCompat; 8 | import android.util.AttributeSet; 9 | import android.view.Gravity; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.TextView; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Author: andy.xwt 18 | * Date: 2019-07-27 20:06 19 | * Description:该Behavior只负责测量与布局基本与{@link HeaderScrollingViewBehavior}非常类似。 20 | * 这里为了帮助大家理解如何理解在如何在CoordinatorLayout下,通过Behavior来控制控件的位置与高度 21 | */ 22 | 23 | public class MeasureLayoutBehavior extends CoordinatorLayout.Behavior { 24 | 25 | 26 | final Rect mTempRect1 = new Rect(); 27 | final Rect mTempRect2 = new Rect(); 28 | 29 | public static final String TAG = "MeasureLayoutBehavior"; 30 | 31 | public MeasureLayoutBehavior() { 32 | } 33 | 34 | public MeasureLayoutBehavior(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | } 37 | 38 | 39 | /** 40 | * 依赖TextView 41 | */ 42 | @Override 43 | public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { 44 | return dependency instanceof TextView; 45 | } 46 | 47 | 48 | @Override 49 | public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 50 | 51 | //获取当前滚动控件的测量模式 52 | final int childLpHeight = child.getLayoutParams().height; 53 | 54 | //只有当前滚动控件为match_parent/wrap_content时才重新测量其高度,因为固定高度不会出现底部空白的情况 55 | if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT 56 | || childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { 57 | 58 | //获取当前child依赖的对象集合 59 | final List dependencies = parent.getDependencies(child); 60 | 61 | final View header = findFirstDependency(dependencies); 62 | if (header != null) { 63 | if (ViewCompat.getFitsSystemWindows(header) 64 | && !ViewCompat.getFitsSystemWindows(child)) { 65 | // If the header is fitting system windows then we need to also, 66 | // otherwise we'll get CoL's compatible measuring 67 | ViewCompat.setFitsSystemWindows(child, true); 68 | 69 | if (ViewCompat.getFitsSystemWindows(child)) { 70 | // If the set succeeded, trigger a new layout and return true 71 | child.requestLayout(); 72 | return true; 73 | } 74 | } 75 | //获取当前父控件中可用的距离, 76 | int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec); 77 | if (availableHeight == 0) { 78 | 79 | // If the measure spec doesn't specify a size, use the current height 80 | availableHeight = parent.getHeight(); 81 | } 82 | //计算当前滚动控件的高度。 83 | final int height = availableHeight - header.getMeasuredHeight() + getScrollRange(header); 84 | final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, 85 | childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT 86 | ? View.MeasureSpec.EXACTLY 87 | : View.MeasureSpec.AT_MOST); 88 | 89 | //测量当前滚动的View的正确高度 90 | parent.onMeasureChild(child, parentWidthMeasureSpec, 91 | widthUsed, heightMeasureSpec, heightUsed); 92 | 93 | return true; 94 | } 95 | } 96 | return false; 97 | } 98 | 99 | @Override 100 | public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) { 101 | final List dependencies = parent.getDependencies(child); 102 | final View header = findFirstDependency(dependencies); 103 | 104 | if (header != null) { 105 | final CoordinatorLayout.LayoutParams lp = 106 | (CoordinatorLayout.LayoutParams) child.getLayoutParams(); 107 | final Rect available = mTempRect1; 108 | 109 | //设置当前的宽高 为当前header的下方 110 | available.set(parent.getPaddingLeft() + lp.leftMargin, 111 | header.getBottom() + lp.topMargin, 112 | parent.getWidth() - parent.getPaddingRight() - lp.rightMargin, 113 | parent.getHeight() + header.getBottom() 114 | - parent.getPaddingBottom() - lp.bottomMargin); 115 | 116 | //根据gravity重新计算坐标 117 | final Rect out = mTempRect2; 118 | GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(), 119 | child.getMeasuredHeight(), available, out, layoutDirection); 120 | 121 | //拿到坐标后重新布局 122 | child.layout(out.left, out.top, out.right, out.bottom); 123 | 124 | } else { 125 | //如果没有依赖,则调用父控件来处理布局 126 | parent.onLayoutChild(child, layoutDirection); 127 | } 128 | return true; 129 | } 130 | 131 | 132 | /** 133 | * 从依赖集合中获取第一个 134 | */ 135 | View findFirstDependency(List views) { 136 | if (views != null && !views.isEmpty()) { 137 | return views.get(0); 138 | } 139 | return null; 140 | } 141 | 142 | /** 143 | * 矫正当前Gravity 144 | */ 145 | private static int resolveGravity(int gravity) { 146 | return gravity == Gravity.NO_GRAVITY ? GravityCompat.START | Gravity.TOP : gravity; 147 | } 148 | 149 | 150 | /** 151 | * 获取当前View的滑动范围,一般情况下,为view的高度 152 | * 153 | * @param v 154 | * @return 155 | */ 156 | int getScrollRange(View v) { 157 | return v.getMeasuredHeight(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/behavior/NestedHeaderBehavior.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl.behavior; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.design.widget.CoordinatorLayout; 6 | import android.support.v4.view.ViewCompat; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import java.lang.ref.WeakReference; 13 | 14 | /** 15 | * Author: andy.xwt 16 | * Date: 2019-07-22 23:24 17 | * Description: 处理嵌套滑动的Behavior,仿照{@link android.support.design.widget.BottomSheetBehavior} 18 | * 对嵌套滑动相关方法不熟悉的的小伙伴可以查看{@link com.jennifer.andy.nestedscrollingdemo.ui.nested.normal_form.NestedScrollingParent2View} 19 | * 其实这里可以使用android.support.design.widget.ViewOffsetHelper,熟悉的小伙伴可以自己改造。 20 | */ 21 | 22 | public class NestedHeaderBehavior extends CoordinatorLayout.Behavior { 23 | 24 | 25 | private WeakReference mNestedScrollingChildRef; 26 | 27 | public static final String TAG = "NestedHeaderBehavior"; 28 | 29 | private int mOffset;//记录当前布局的偏移量 30 | 31 | public NestedHeaderBehavior(Context context, AttributeSet attrs) { 32 | super(context, attrs); 33 | } 34 | 35 | @Override 36 | public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) { 37 | mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(parent)); 38 | return super.onLayoutChild(parent, child, layoutDirection); 39 | } 40 | 41 | 42 | @Override 43 | public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) { 44 | //只要竖直方向上就拦截 45 | return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 46 | } 47 | 48 | 49 | @Override 50 | public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { 51 | View scrollingChild = mNestedScrollingChildRef.get(); 52 | if (target != scrollingChild) { 53 | return; 54 | } 55 | int currentTop = child.getTop(); 56 | int newTop = currentTop - dy; 57 | if (dy > 0) {//向上滑动 58 | //处理在范围内的滚动与fling 59 | if (newTop >= -child.getHeight()) { 60 | Log.i(TAG, "onNestedPreScroll:向上移动" + "currentTop--->" + currentTop + " newTop--->" + newTop); 61 | consumed[1] = dy; 62 | mOffset = -dy; 63 | ViewCompat.offsetTopAndBottom(child, -dy); 64 | coordinatorLayout.dispatchDependentViewsChanged(child); 65 | } else { //当超过后,单独处理 66 | consumed[1] = child.getHeight() + currentTop; 67 | mOffset = -consumed[1]; 68 | ViewCompat.offsetTopAndBottom(child, -consumed[1]); 69 | coordinatorLayout.dispatchDependentViewsChanged(child); 70 | } 71 | } 72 | if (dy < 0) {//向下滑动 73 | if (newTop <= 0 && !target.canScrollVertically(-1)) { 74 | Log.i(TAG, "onNestedPreScroll:向下移动" + "currentTop--->" + currentTop + " newTop--->" + newTop); 75 | consumed[1] = dy; 76 | mOffset = -dy; 77 | ViewCompat.offsetTopAndBottom(child, -dy); 78 | coordinatorLayout.dispatchDependentViewsChanged(child); 79 | } 80 | } 81 | 82 | } 83 | 84 | 85 | @Override 86 | public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { 87 | if (dyUnconsumed < 0) {//表示已经向下滑动到头。 88 | int currentTop = child.getTop(); 89 | int newTop = currentTop - dyUnconsumed; 90 | if (newTop <= 0) { 91 | Log.i(TAG, "onNestedScroll: " + "dyUnconsumed--> " + dyUnconsumed + " currentTop--->" + currentTop + " newTop--->" + newTop); 92 | ViewCompat.offsetTopAndBottom(child, -dyUnconsumed); 93 | mOffset = -dyUnconsumed; 94 | } else {//如果当前的值大于最大的偏移量,那么就直接滚动到-currentTop就行了 95 | ViewCompat.offsetTopAndBottom(child, -currentTop); 96 | mOffset = -currentTop; 97 | } 98 | coordinatorLayout.dispatchDependentViewsChanged(child); 99 | } 100 | 101 | } 102 | 103 | /** 104 | * 这里是为了解决在CoordinatorLayout下,RecyclerView调用smoothScrollToPosition导致嵌套滑动效果失效的问题 105 | * 如有需要可将注释打开 106 | */ 107 | 108 | // @Override 109 | // public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) { 110 | // super.onStopNestedScroll(coordinatorLayout, child, target, type); 111 | // String message = type == ViewCompat.TYPE_NON_TOUCH ? "TYPE_NON_TOUCH" : "TYPE_TOUCH"; 112 | // Log.i(TAG, "onStopNestedScroll: " + message); 113 | // if (type == ViewCompat.TYPE_NON_TOUCH) { 114 | // if (!target.canScrollVertically(-1)) { 115 | // ViewCompat.offsetTopAndBottom(child, -child.getTop()); 116 | // coordinatorLayout.dispatchDependentViewsChanged(child); 117 | // } 118 | // } 119 | // 120 | // } 121 | 122 | /** 123 | * 获取实现了NestedScrollingChild或NestedScrollingChild2接口的View。 124 | */ 125 | private View findScrollingChild(View view) { 126 | if (ViewCompat.isNestedScrollingEnabled(view)) { 127 | return view; 128 | } 129 | if (view instanceof ViewGroup) { 130 | ViewGroup group = (ViewGroup) view; 131 | for (int i = 0, count = group.getChildCount(); i < count; i++) { 132 | View scrollingChild = findScrollingChild(group.getChildAt(i)); 133 | if (scrollingChild != null) { 134 | return scrollingChild; 135 | } 136 | } 137 | } 138 | return null; 139 | } 140 | 141 | /** 142 | * 获取当前控件的偏移量 143 | */ 144 | public int getOffset() { 145 | return mOffset; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/cdl/behavior/ScrollingViewBehavior.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.cdl.behavior; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.CoordinatorLayout; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import com.jennifer.andy.nestedscrollingdemo.ui.cdl.CoordinatorLayoutDemo4Activity; 10 | 11 | /** 12 | * Author: andy.xwt 13 | * Date: 2019-07-24 22:22 14 | * Description: 在{@link CoordinatorLayoutDemo4Activity}中RecyclerView重新布局中需要的Behavior 15 | */ 16 | 17 | public class ScrollingViewBehavior extends HeaderScrollingViewBehavior { 18 | 19 | 20 | public ScrollingViewBehavior(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | } 23 | 24 | /** 25 | * 依赖TextView 26 | */ 27 | @Override 28 | public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { 29 | return dependency instanceof TextView; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/nested/NestedScrolling2DemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.nested; 2 | 3 | import android.animation.ArgbEvaluator; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.Drawable; 6 | import android.os.Bundle; 7 | import android.support.annotation.DrawableRes; 8 | import android.support.design.widget.TabLayout; 9 | import android.support.v4.app.Fragment; 10 | import android.support.v4.graphics.drawable.DrawableCompat; 11 | import android.support.v4.view.ViewPager; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.view.View; 14 | import android.widget.ImageView; 15 | import android.widget.TextView; 16 | 17 | import com.jennifer.andy.nestedscrollingdemo.R; 18 | import com.jennifer.andy.nestedscrollingdemo.adapter.BaseFragmentItemAdapter; 19 | import com.jennifer.andy.nestedscrollingdemo.ui.TabFragment; 20 | import com.jennifer.andy.nestedscrollingdemo.view.StickyNavLayout; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | /** 26 | * Author: andy.xwt 27 | * Date: 2019-07-08 22:11 28 | * Description: 29 | */ 30 | 31 | public class NestedScrolling2DemoActivity extends AppCompatActivity { 32 | 33 | 34 | private TabLayout mTabLayout; 35 | private ViewPager mViewPager; 36 | private StickyNavLayout mStickyNavLayout; 37 | private ImageView mBackImageView; 38 | private TextView mTitleView; 39 | 40 | public static final int FRAGMENT_COUNT = 4; 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_nested_scrolling2_demo); 46 | findView(); 47 | initData(); 48 | } 49 | 50 | private void findView() { 51 | mTabLayout = findViewById(R.id.sl_tab); 52 | mViewPager = findViewById(R.id.sl_viewpager); 53 | mStickyNavLayout = findViewById(R.id.sick_layout); 54 | mBackImageView = findViewById(R.id.iv_back); 55 | mTitleView = findViewById(R.id.tv_title); 56 | 57 | initToolBar(R.drawable.ic_action_back_black, 0); 58 | } 59 | 60 | private void initData() { 61 | mViewPager.setAdapter(new BaseFragmentItemAdapter(getSupportFragmentManager(), initFragments(), initTitles())); 62 | mTabLayout.setupWithViewPager(mViewPager); 63 | mStickyNavLayout.setScrollChangeListener(new StickyNavLayout.ScrollChangeListener() { 64 | @Override 65 | public void onScroll(float moveRatio) { 66 | initToolBar(R.drawable.ic_action_back_white, moveRatio); 67 | } 68 | }); 69 | } 70 | 71 | private void initToolBar(@DrawableRes int backResId, float moveRatio) { 72 | mBackImageView.setOnClickListener(new View.OnClickListener() { 73 | @Override 74 | public void onClick(View v) { 75 | finish(); 76 | } 77 | }); 78 | 79 | ArgbEvaluator argbEvaluator = new ArgbEvaluator(); 80 | int color = (int) argbEvaluator.evaluate(moveRatio, Color.WHITE, Color.BLACK); 81 | Drawable wrapDrawable = DrawableCompat.wrap(getResources().getDrawable(backResId)); 82 | DrawableCompat.setTint(wrapDrawable, color); 83 | 84 | mBackImageView.setImageDrawable(wrapDrawable); 85 | mTitleView.setAlpha(moveRatio); 86 | 87 | 88 | } 89 | 90 | private List initFragments() { 91 | List fragments = new ArrayList<>(); 92 | for (int i = 0; i < FRAGMENT_COUNT; i++) { 93 | fragments.add(TabFragment.newInstance("NestedScrolling2Demo")); 94 | } 95 | return fragments; 96 | } 97 | 98 | private List initTitles() { 99 | List titles = new ArrayList<>(); 100 | titles.add("首页"); 101 | titles.add("全部"); 102 | titles.add("作者"); 103 | titles.add("专辑"); 104 | return titles; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/nested/NestedScrollingParent2Activity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.nested; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.TabLayout; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.view.ViewPager; 7 | import android.support.v7.app.AppCompatActivity; 8 | 9 | import com.jennifer.andy.nestedscrollingdemo.R; 10 | import com.jennifer.andy.nestedscrollingdemo.adapter.BaseFragmentItemAdapter; 11 | import com.jennifer.andy.nestedscrollingdemo.ui.TabFragment; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * Author: andy.xwt 18 | * Date: 2018/8/8 13:56 19 | * Description:使用NestedScrollingParent2的实现嵌套滑动 20 | */ 21 | 22 | public class NestedScrollingParent2Activity extends AppCompatActivity { 23 | 24 | private TabLayout mTabLayout; 25 | private ViewPager mViewPager; 26 | 27 | public static final int FRAGMENT_COUNT = 4; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_nested_srolling_parent2); 33 | findView(); 34 | initData(); 35 | } 36 | 37 | private void findView() { 38 | mTabLayout = findViewById(R.id.tab_layout); 39 | mViewPager = findViewById(R.id.view_pager); 40 | } 41 | 42 | private void initData() { 43 | mViewPager.setAdapter(new BaseFragmentItemAdapter(getSupportFragmentManager(), initFragments(), initTitles())); 44 | mViewPager.setOffscreenPageLimit(FRAGMENT_COUNT); 45 | mTabLayout.setupWithViewPager(mViewPager); 46 | } 47 | 48 | private List initFragments() { 49 | List fragments = new ArrayList<>(); 50 | for (int i = 0; i < FRAGMENT_COUNT; i++) { 51 | fragments.add(TabFragment.newInstance("实现NestedScrollingParent2接口")); 52 | } 53 | return fragments; 54 | } 55 | 56 | private List initTitles() { 57 | List titles = new ArrayList<>(); 58 | titles.add("首页"); 59 | titles.add("全部"); 60 | titles.add("作者"); 61 | titles.add("专辑"); 62 | return titles; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/nested/NestedScrollingParentActivity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.nested; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.TabLayout; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.view.ViewPager; 7 | import android.support.v7.app.AppCompatActivity; 8 | 9 | import com.jennifer.andy.nestedscrollingdemo.R; 10 | import com.jennifer.andy.nestedscrollingdemo.adapter.BaseFragmentItemAdapter; 11 | import com.jennifer.andy.nestedscrollingdemo.ui.TabFragment; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * Author: andy.xwt 18 | * Date: 2018/8/8 13:56 19 | * Description:使用NestedScrollingParent接口的嵌套滑动 20 | */ 21 | 22 | public class NestedScrollingParentActivity extends AppCompatActivity { 23 | 24 | private TabLayout mTabLayout; 25 | private ViewPager mViewPager; 26 | 27 | public static final int FRAGMENT_COUNT = 4; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_nested_srolling_parent); 33 | findView(); 34 | initData(); 35 | } 36 | 37 | private void findView() { 38 | mTabLayout = findViewById(R.id.tab_layout); 39 | mViewPager = findViewById(R.id.view_pager); 40 | } 41 | 42 | private void initData() { 43 | mViewPager.setAdapter(new BaseFragmentItemAdapter(getSupportFragmentManager(), initFragments(), initTitles())); 44 | mViewPager.setOffscreenPageLimit(FRAGMENT_COUNT); 45 | mTabLayout.setupWithViewPager(mViewPager); 46 | } 47 | 48 | private List initFragments() { 49 | List fragments = new ArrayList<>(); 50 | for (int i = 0; i < FRAGMENT_COUNT; i++) { 51 | fragments.add(TabFragment.newInstance("实现NestedScrollingParent接口")); 52 | } 53 | return fragments; 54 | } 55 | 56 | private List initTitles() { 57 | List titles = new ArrayList<>(); 58 | titles.add("首页"); 59 | titles.add("全部"); 60 | titles.add("作者"); 61 | titles.add("专辑"); 62 | return titles; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/nested/NestedTraditionActivity.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.nested; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.TabLayout; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.view.ViewPager; 7 | import android.support.v7.app.AppCompatActivity; 8 | 9 | import com.jennifer.andy.nestedscrollingdemo.R; 10 | import com.jennifer.andy.nestedscrollingdemo.adapter.BaseFragmentItemAdapter; 11 | import com.jennifer.andy.nestedscrollingdemo.ui.TabFragment; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * Author: andy.xwt 18 | * Date: 2018/8/8 13:56 19 | * Description:使用传统机制来实现嵌套滑动 20 | */ 21 | 22 | public class NestedTraditionActivity extends AppCompatActivity { 23 | 24 | 25 | private TabLayout mTabLayout; 26 | private ViewPager mViewPager; 27 | 28 | public static final int FRAGMENT_COUNT = 4; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_nested_tradition); 34 | findView(); 35 | initData(); 36 | } 37 | 38 | private void findView() { 39 | mTabLayout = findViewById(R.id.tab_layout); 40 | mViewPager = findViewById(R.id.view_pager); 41 | } 42 | 43 | private void initData() { 44 | mViewPager.setAdapter(new BaseFragmentItemAdapter(getSupportFragmentManager(), initFragments(), initTitles())); 45 | mViewPager.setOffscreenPageLimit(FRAGMENT_COUNT); 46 | mTabLayout.setupWithViewPager(mViewPager); 47 | } 48 | 49 | private List initFragments() { 50 | List fragments = new ArrayList<>(); 51 | for (int i = 0; i < FRAGMENT_COUNT; i++) { 52 | fragments.add(TabFragment.newInstance("传统事件分发机制嵌套滑动")); 53 | } 54 | return fragments; 55 | } 56 | 57 | private List initTitles() { 58 | List titles = new ArrayList<>(); 59 | titles.add("首页"); 60 | titles.add("全部"); 61 | titles.add("作者"); 62 | titles.add("专辑"); 63 | return titles; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/nested/normal_form/NestedScrollingChild2View.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.nested.normal_form; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.view.NestedScrollingChild2; 6 | import android.support.v4.view.NestedScrollingChildHelper; 7 | import android.support.v4.view.ViewCompat; 8 | import android.view.MotionEvent; 9 | import android.view.VelocityTracker; 10 | import android.view.View; 11 | import android.view.ViewConfiguration; 12 | import android.widget.OverScroller; 13 | 14 | import static android.support.v4.view.ViewCompat.TYPE_NON_TOUCH; 15 | 16 | /** 17 | * Author: andy.xwt 18 | * Date: 2019-06-28 00:12 19 | * Description: NestedScrollingChild2与NestedScrollingChild接口的最大差异就是处理fling, 20 | * 所以我们直接查看fling效果的处理 21 | */ 22 | 23 | public class NestedScrollingChild2View extends View implements NestedScrollingChild2 { 24 | 25 | private NestedScrollingChildHelper mScrollingChildHelper = new NestedScrollingChildHelper(this); 26 | private final int mMinFlingVelocity; 27 | private final int mMaxFlingVelocity; 28 | private OverScroller mScroller; 29 | 30 | public NestedScrollingChild2View(Context context) { 31 | super(context); 32 | ViewConfiguration vc = ViewConfiguration.get(context); 33 | mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 34 | mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); 35 | mScroller = new OverScroller(context); 36 | } 37 | 38 | 39 | @Override 40 | public boolean startNestedScroll(int axes, int type) { 41 | return mScrollingChildHelper.startNestedScroll(axes); 42 | } 43 | 44 | 45 | @Override 46 | public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, int type) { 47 | return mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type); 48 | } 49 | 50 | 51 | @Override 52 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, int type) { 53 | return mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type); 54 | } 55 | 56 | @Override 57 | public void stopNestedScroll(int type) { 58 | mScrollingChildHelper.stopNestedScroll(type); 59 | } 60 | 61 | @Override 62 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 63 | return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); 64 | } 65 | 66 | @Override 67 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 68 | return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 69 | } 70 | 71 | 72 | @Override 73 | public void setNestedScrollingEnabled(boolean enabled) { 74 | mScrollingChildHelper.setNestedScrollingEnabled(enabled); 75 | } 76 | 77 | @Override 78 | public boolean isNestedScrollingEnabled() { 79 | return mScrollingChildHelper.isNestedScrollingEnabled(); 80 | } 81 | 82 | @Override 83 | public boolean hasNestedScrollingParent(int type) { 84 | return mScrollingChildHelper.hasNestedScrollingParent(type); 85 | } 86 | 87 | 88 | private VelocityTracker mVelocityTracker; 89 | 90 | @Override 91 | public boolean onTouchEvent(MotionEvent event) { 92 | int action = event.getActionMasked(); 93 | 94 | int y = (int) event.getY(); 95 | int x = (int) event.getX(); 96 | 97 | //添加速度检测器,用于处理fling效果 98 | if (mVelocityTracker == null) { 99 | mVelocityTracker = VelocityTracker.obtain(); 100 | } 101 | mVelocityTracker.addMovement(event); 102 | 103 | switch (action) { 104 | case MotionEvent.ACTION_UP: {//当手指抬起的时,结束嵌套滑动传递,并判断是否产生了fling效果 105 | mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); 106 | int xvel = (int) mVelocityTracker.getXVelocity(); 107 | int yvel = (int) mVelocityTracker.getYVelocity(); 108 | fling(xvel, yvel); 109 | mVelocityTracker.clear(); 110 | stopNestedScroll(ViewCompat.TYPE_TOUCH); 111 | break; 112 | } 113 | 114 | } 115 | 116 | return super.onTouchEvent(event); 117 | } 118 | 119 | private boolean fling(int velocityX, int velocityY) { 120 | //判断速度是否足够大。如果够大才执行fling 121 | if (Math.abs(velocityX) < mMinFlingVelocity) { 122 | velocityX = 0; 123 | } 124 | if (Math.abs(velocityY) < mMinFlingVelocity) { 125 | velocityY = 0; 126 | } 127 | if (velocityX == 0 && velocityY == 0) { 128 | return false; 129 | } 130 | if (dispatchNestedPreFling(velocityX, velocityY)) { 131 | boolean canScroll = canScroll(); 132 | //将fling效果传递给父控件 133 | dispatchNestedFling(velocityX, velocityY, canScroll); 134 | 135 | //子控件在处理fling效果 136 | if (canScroll) { 137 | //通知父控件开始fling事件,注意这里默认传递的是竖直方向,具体方向由子控件决定 138 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH); 139 | velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); 140 | velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); 141 | doFling(velocityX, velocityY); 142 | return true; 143 | } 144 | 145 | } 146 | return false; 147 | 148 | } 149 | 150 | private int mLastFlingX; 151 | private int mLastFlingY; 152 | private final int[] mScrollConsumed = new int[2]; 153 | 154 | /** 155 | * 实际的fling处理效果 156 | */ 157 | private void doFling(int velocityX, int velocityY) { 158 | mScroller.fling(0, 0, velocityX, velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 159 | postInvalidate(); 160 | } 161 | 162 | @Override 163 | public void computeScroll() { 164 | if (mScroller.computeScrollOffset()) { 165 | int x = mScroller.getCurrX(); 166 | int y = mScroller.getCurrY(); 167 | int dx = x - mLastFlingX; 168 | int dy = y - mLastFlingY; 169 | 170 | mLastFlingX = x; 171 | mLastFlingY = y; 172 | //在子控件处理fling之前,先判断父控件是否消耗 173 | if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, null, TYPE_NON_TOUCH)) { 174 | //计算父控件消耗后,剩下的距离 175 | dx -= mScrollConsumed[0]; 176 | dy -= mScrollConsumed[1]; 177 | 178 | //因为之前默认向父控件传递的竖直方向,所以这里子控件也消耗剩下的竖直方向 179 | int hResult = 0; 180 | int vResult = 0; 181 | int leaveDx = 0;//子控件水平fling 消耗的距离 182 | int leaveDy = 0;//父控件竖直fling 消耗的距离 183 | 184 | if (dx != 0) { 185 | leaveDx = childFlingX(dx); 186 | hResult = dx - leaveDx;//得到子控件消耗后剩下的水平距离 187 | } 188 | if (dy != 0) { 189 | leaveDy = childFlingY(dy);//得到子控件消耗后剩下的竖直距离 190 | vResult = dy - leaveDy; 191 | } 192 | 193 | dispatchNestedScroll(leaveDx, leaveDy, hResult, vResult, null, TYPE_NON_TOUCH); 194 | 195 | } 196 | } else { 197 | stopNestedScroll(TYPE_NON_TOUCH); 198 | 199 | } 200 | 201 | } 202 | 203 | /** 204 | * 判断子子控件是否能够滑动,只有能滑动才能处理fling 205 | */ 206 | private boolean canScroll() { 207 | //具体逻辑自己实现 208 | return false; 209 | } 210 | 211 | /** 212 | * 子控件消耗多少竖直方向上的fling,由子控件自己决定 213 | * 214 | * @param dy 父控件消耗部分竖直fling后,剩余的距离 215 | * @return 子控件竖直fling,消耗的距离 216 | */ 217 | private int childFlingY(int dy) { 218 | 219 | return 0; 220 | } 221 | 222 | /** 223 | * 子控件消耗多少竖直方向上的fling,由子控件自己决定 224 | * 225 | * @param dx 父控件消耗部分水平fling后,剩余的距离 226 | * @return 子控件水平fling,消耗的距离 227 | */ 228 | private int childFlingX(int dx) { 229 | return 0; 230 | } 231 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/nested/normal_form/NestedScrollingChildView.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.nested.normal_form; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.view.NestedScrollingChild; 6 | import android.support.v4.view.NestedScrollingChildHelper; 7 | import android.support.v4.view.ViewCompat; 8 | import android.view.MotionEvent; 9 | import android.view.VelocityTracker; 10 | import android.view.View; 11 | import android.view.ViewConfiguration; 12 | 13 | /** 14 | * Author: andy.xwt 15 | * Date: 2019-06-28 00:12 16 | * Description: 嵌套滑动中,实现NestedScrollingChild接口的基本范式代码 17 | */ 18 | 19 | public class NestedScrollingChildView extends View implements NestedScrollingChild { 20 | 21 | private NestedScrollingChildHelper mScrollingChildHelper = new NestedScrollingChildHelper(this); 22 | 23 | private final int mMinFlingVelocity; 24 | private final int mMaxFlingVelocity; 25 | 26 | 27 | public NestedScrollingChildView(Context context) { 28 | super(context); 29 | ViewConfiguration vc = ViewConfiguration.get(context); 30 | mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 31 | mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); 32 | } 33 | 34 | 35 | /** 36 | * 开启一个嵌套滑动 37 | * 38 | * @param axes 支持的嵌套滑动方法,分为水平方向,竖直方向,或不指定 39 | * @return 如果返回true, 表示当前子view已经找了一起嵌套滑动的view 40 | */ 41 | @Override 42 | public boolean startNestedScroll(int axes) { 43 | return mScrollingChildHelper.startNestedScroll(axes); 44 | } 45 | 46 | 47 | /** 48 | * 在子view滑动前,将事件分发给父view,由父view判断消耗多少 49 | * 50 | * @param dx 水平方向嵌套滑动的子View想要变化的距离 dx<0 向右滑动 dx>0 向左滑动 51 | * @param dy 垂直方向嵌套滑动的子View想要变化的距离 dy<0 向下滑动 dy>0 向上滑动 52 | * @param consumed 子view传给父view数组,用于存储父view水平与竖直方向上消耗的距离,consumed[0] 水平消耗的距离,consumed[1] 垂直消耗的距离 53 | * @param offsetInWindow 子view在当前window的偏移量 54 | * @return 如果返回true, 表示父view已经消耗了 55 | */ 56 | @Override 57 | public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) { 58 | return mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 59 | } 60 | 61 | 62 | /** 63 | * 当父view消耗事件后,子view处理后,又继续将事件分发给父view,由父view判断是否消耗剩下的距离。 64 | * 65 | * @param dxConsumed 水平方向嵌套滑动的子View滑动的距离(消耗的距离) 66 | * @param dyConsumed 垂直方向嵌套滑动的子View滑动的距离(消耗的距离) 67 | * @param dxUnconsumed 水平方向嵌套滑动的子View未滑动的距离(未消耗的距离) 68 | * @param dyUnconsumed 垂直方向嵌套滑动的子View未滑动的距离(未消耗的距离) 69 | * @param offsetInWindow 子view在当前window的偏移量 70 | * @return 如果返回true, 表示父view又继续消耗了 71 | */ 72 | @Override 73 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) { 74 | return mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); 75 | } 76 | 77 | /** 78 | * 子view停止嵌套滑动 79 | */ 80 | @Override 81 | public void stopNestedScroll() { 82 | mScrollingChildHelper.stopNestedScroll(); 83 | } 84 | 85 | 86 | /** 87 | * 当子view产生fling滑动时,判断父view是否处拦截fling,如果父View处理了fling,那子view就没有办法处理fling了。 88 | * 89 | * @param velocityX 水平方向上的速度 velocityX > 0 向左滑动,反之向右滑动 90 | * @param velocityY 竖直方向上的速度 velocityY > 0 向上滑动,反之向下滑动 91 | * @return 如果返回true, 表示父view拦截了fling 92 | */ 93 | @Override 94 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 95 | return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); 96 | } 97 | 98 | /** 99 | * 当父view不拦截子view的fling,那么子view会调用该方法将fling,传给父view进行处理 100 | * 101 | * @param velocityX 水平方向上的速度 velocityX > 0 向左滑动,反之向右滑动 102 | * @param velocityY 竖直方向上的速度 velocityY > 0 向上滑动,反之向下滑动 103 | * @param consumed 子view是否可以消耗该fling,也可以说是子view是否消耗掉了该fling 104 | * @return 父view是否消耗了该fling 105 | */ 106 | @Override 107 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 108 | return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 109 | } 110 | 111 | /** 112 | * 设置当前子view是否支持嵌套滑动,如果不支持,那么父view是不能够响应嵌套滑动的 113 | * 114 | * @param enabled true 支持 115 | */ 116 | @Override 117 | public void setNestedScrollingEnabled(boolean enabled) { 118 | mScrollingChildHelper.setNestedScrollingEnabled(enabled); 119 | } 120 | 121 | /** 122 | * 当前子view是否支持嵌套滑动 123 | */ 124 | @Override 125 | public boolean isNestedScrollingEnabled() { 126 | return mScrollingChildHelper.isNestedScrollingEnabled(); 127 | } 128 | 129 | /** 130 | * 判断当前子view是否拥有嵌套滑动的父view 131 | */ 132 | @Override 133 | public boolean hasNestedScrollingParent() { 134 | return mScrollingChildHelper.hasNestedScrollingParent(); 135 | } 136 | 137 | private int mLastY; 138 | private int mLastX; 139 | private final int[] mScrollConsumed = new int[2]; 140 | private final int[] mScrollOffset = new int[2]; 141 | private VelocityTracker mVelocityTracker; 142 | 143 | @Override 144 | public boolean onTouchEvent(MotionEvent event) { 145 | 146 | int action = event.getActionMasked(); 147 | 148 | int y = (int) event.getY(); 149 | int x = (int) event.getX(); 150 | 151 | //添加速度检测器,用于处理fling效果 152 | if (mVelocityTracker == null) { 153 | mVelocityTracker = VelocityTracker.obtain(); 154 | } 155 | mVelocityTracker.addMovement(event); 156 | 157 | switch (action) { 158 | case MotionEvent.ACTION_DOWN: { 159 | mLastX = x; 160 | mLastY = y; 161 | //自己的处理逻辑,判断传递竖直还是水平方向,这里默认是设置的竖直方向 162 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); 163 | break; 164 | } 165 | case MotionEvent.ACTION_MOVE: { 166 | int dy = mLastY - y; 167 | int dx = mLastX - x; 168 | 169 | //将事件传递给父控件,并记录父控件消耗的距离。 170 | if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) { 171 | dx -= mScrollConsumed[0]; 172 | dy -= mScrollConsumed[1]; 173 | //子控件处理事件,并将未处理完的事件传递给父控件 174 | scrollNested(dx, dy); 175 | } 176 | //如果找不到嵌套滑动的父控件,自己就处理事件。 177 | childScroll(dx, dy); 178 | break; 179 | } 180 | case MotionEvent.ACTION_UP: {//当手指抬起的时,结束嵌套滑动传递,并判断是否产生了fling效果 181 | mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); 182 | int xvel = (int) mVelocityTracker.getXVelocity(); 183 | int yvel = (int) mVelocityTracker.getYVelocity(); 184 | fling(xvel, yvel); 185 | mVelocityTracker.clear(); 186 | stopNestedScroll(); 187 | break; 188 | } 189 | case MotionEvent.ACTION_CANCEL: {//当取消滑动的时,结束嵌套滑动传递 190 | stopNestedScroll(); 191 | mVelocityTracker.clear(); 192 | break; 193 | } 194 | } 195 | 196 | return super.onTouchEvent(event); 197 | } 198 | 199 | /** 200 | * 子控件处理事件,并将未处理完的事件传递给父控件 201 | * 202 | * @param x 水平方向移动距离 203 | * @param y 竖直方向移动距离 204 | */ 205 | private void scrollNested(int x, int y) { 206 | int unConsumedX = 0, unConsumedY = 0; 207 | int consumedX = 0, consumedY = 0; 208 | 209 | //子控件消耗多少事件,由自己决定 210 | if (x != 0) { 211 | consumedX = childConsumeX(x); 212 | unConsumedX = x - consumedX; 213 | } 214 | if (y != 0) { 215 | consumedY = childConsumeY(y); 216 | unConsumedY = y - consumedY; 217 | } 218 | 219 | //子控件处理事件 220 | childScroll(consumedX, consumedY); 221 | 222 | //子控件处理后,又将剩下的事件传递给父控件 223 | if (!dispatchNestedScroll(consumedX, consumedY, unConsumedX, unConsumedY, mScrollOffset)) { 224 | //传给父控件处理后,剩下的逻辑自己实现 225 | } 226 | //传递给父控件,父控件不处理,那么子控件就继续处理。 227 | childScroll(unConsumedX, unConsumedY); 228 | 229 | } 230 | 231 | /** 232 | * 子控件滑动逻辑 233 | */ 234 | private void childScroll(int x, int y) { 235 | //子控件怎么滑动,自己实现 236 | } 237 | 238 | 239 | /** 240 | * 子控件水平方向消耗多少距离 241 | */ 242 | private int childConsumeX(int x) { 243 | //具体逻辑由自己实现 244 | return 0; 245 | } 246 | 247 | /** 248 | * 子控件竖直方向消耗距离 249 | */ 250 | private int childConsumeY(int y) { 251 | //具体逻辑由自己实现 252 | return 0; 253 | } 254 | 255 | private boolean fling(int velocityX, int velocityY) { 256 | //判断速度是否足够大。如果够大才执行fling 257 | if (Math.abs(velocityX) < mMinFlingVelocity) { 258 | velocityX = 0; 259 | } 260 | if (Math.abs(velocityY) < mMinFlingVelocity) { 261 | velocityY = 0; 262 | } 263 | if (velocityX == 0 && velocityY == 0) { 264 | return false; 265 | } 266 | if (dispatchNestedPreFling(velocityX, velocityY)) { 267 | boolean consumed = canScroll(); 268 | //将fling效果传递给父控件 269 | dispatchNestedFling(velocityX, velocityY, consumed); 270 | //然后子控件在处理fling效果 271 | childFling(); 272 | 273 | } 274 | return false; 275 | 276 | } 277 | 278 | 279 | /** 280 | * 判断子子控件是否能够滑动,只有能滑动才能处理fling 281 | */ 282 | private boolean canScroll() { 283 | //具体逻辑自己实现 284 | return false; 285 | } 286 | 287 | /** 288 | * 子控件处理fling效果 289 | */ 290 | private void childFling() { 291 | //具体逻辑自己实现 292 | } 293 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/nested/normal_form/NestedScrollingParent2View.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.nested.normal_form; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v4.view.NestedScrollingParent2; 6 | import android.support.v4.view.NestedScrollingParentHelper; 7 | import android.view.View; 8 | import android.widget.LinearLayout; 9 | 10 | /** 11 | * Author: andy.xwt 12 | * Date: 2019-06-28 00:13 13 | * Description: 14 | */ 15 | 16 | public class NestedScrollingParent2View extends LinearLayout implements NestedScrollingParent2 { 17 | 18 | private NestedScrollingParentHelper mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 19 | 20 | public NestedScrollingParent2View(Context context) { 21 | super(context); 22 | } 23 | 24 | 25 | /** 26 | * 有嵌套滑动到来了,判断父view是否接受嵌套滑动 27 | * 28 | * @param child 嵌套滑动对应的父类的子类(因为嵌套滑动对于的父View不一定是一级就能找到的,可能挑了两级父View的父View,child的辈分>=target) 29 | * @param target 具体嵌套滑动的那个子类 30 | * @param nestedScrollAxes 支持嵌套滚动轴。水平方向,垂直方向,或者不指定 31 | * @param type 滑动类型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手势滑动 32 | */ 33 | @Override 34 | public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes, int type) { 35 | //自己处理逻辑 36 | return true; 37 | } 38 | 39 | /** 40 | * 当父view接受嵌套滑动,当onStartNestedScroll方法返回true该方法会调用 41 | * 42 | * @param type 滑动类型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手势滑动 43 | */ 44 | @Override 45 | public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) { 46 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type); 47 | } 48 | 49 | /** 50 | * 在嵌套滑动的子View未滑动之前,判断父view是否优先与子view处理(也就是父view可以先消耗,然后给子view消耗) 51 | * 52 | * @param target 具体嵌套滑动的那个子类 53 | * @param dx 水平方向嵌套滑动的子View想要变化的距离 54 | * @param dy 垂直方向嵌套滑动的子View想要变化的距离 dy<0向下滑动 dy>0 向上滑动 55 | * @param consumed 这个参数要我们在实现这个函数的时候指定,回头告诉子View当前父View消耗的距离 56 | * consumed[0] 水平消耗的距离,consumed[1] 垂直消耗的距离 好让子view做出相应的调整 57 | * @param type 滑动类型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手势滑动 58 | */ 59 | @Override 60 | public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { 61 | //自己处理逻辑 62 | } 63 | 64 | /** 65 | * 嵌套滑动的子View在滑动之后,判断父view是否继续处理(也就是父消耗一定距离后,子再消耗,最后判断父消耗不) 66 | * 67 | * @param target 具体嵌套滑动的那个子类 68 | * @param dxConsumed 水平方向嵌套滑动的子View滑动的距离(消耗的距离) 69 | * @param dyConsumed 垂直方向嵌套滑动的子View滑动的距离(消耗的距离) 70 | * @param dxUnconsumed 水平方向嵌套滑动的子View未滑动的距离(未消耗的距离) 71 | * @param dyUnconsumed 垂直方向嵌套滑动的子View未滑动的距离(未消耗的距离) 72 | * @param type 滑动类型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手势滑动 73 | */ 74 | @Override 75 | public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { 76 | //自己处理逻辑 77 | } 78 | 79 | /** 80 | * 嵌套滑动结束 81 | * 82 | * @param type 滑动类型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手势滑动 83 | */ 84 | @Override 85 | public void onStopNestedScroll(@NonNull View child, int type) { 86 | mNestedScrollingParentHelper.onStopNestedScroll(child, type); 87 | } 88 | 89 | @Override 90 | public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { 91 | //自己判断是否处理 92 | return false; 93 | } 94 | 95 | @Override 96 | public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { 97 | //自己处理逻辑 98 | return false; 99 | } 100 | 101 | 102 | @Override 103 | public int getNestedScrollAxes() { 104 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 105 | } 106 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/ui/nested/normal_form/NestedScrollingParentView.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.ui.nested.normal_form; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v4.view.NestedScrollingParent; 6 | import android.support.v4.view.NestedScrollingParentHelper; 7 | import android.view.View; 8 | import android.widget.LinearLayout; 9 | 10 | /** 11 | * Author: andy.xwt 12 | * Date: 2019-06-28 00:13 13 | * Description: 14 | */ 15 | 16 | public class NestedScrollingParentView extends LinearLayout implements NestedScrollingParent { 17 | 18 | private NestedScrollingParentHelper mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 19 | 20 | public NestedScrollingParentView(Context context) { 21 | super(context); 22 | } 23 | 24 | /** 25 | * 有嵌套滑动到来了,判断父view是否接受嵌套滑动 26 | * 27 | * @param child 嵌套滑动对应的父类的子类(因为嵌套滑动对于的父View不一定是一级就能找到的,可能挑了两级父View的父View,child的辈分>=target) 28 | * @param target 具体嵌套滑动的那个子类 29 | * @param nestedScrollAxes 支持嵌套滚动轴。水平方向,垂直方向,或者不指定 30 | * @return 父view是否接受嵌套滑动, 只有接受了才会执行剩下的嵌套滑动方法 31 | */ 32 | @Override 33 | public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes) { 34 | return super.onStartNestedScroll(child, target, nestedScrollAxes); 35 | } 36 | 37 | /** 38 | * 当onStartNestedScroll返回为true时,也就是父view接受嵌套滑动时,该方法才会调用 39 | */ 40 | @Override 41 | public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) { 42 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); 43 | } 44 | 45 | /** 46 | * 在嵌套滑动的子View未滑动之前,判断父view是否优先与子view处理(也就是父view可以先消耗,然后给子view消耗) 47 | * 48 | * @param target 具体嵌套滑动的那个子类 49 | * @param dx 水平方向嵌套滑动的子View想要变化的距离 50 | * @param dy 垂直方向嵌套滑动的子View想要变化的距离 dy<0向下滑动 dy>0 向上滑动 51 | * @param consumed 这个参数要我们在实现这个函数的时候指定,回头告诉子View当前父View消耗的距离 52 | * consumed[0] 水平消耗的距离,consumed[1] 垂直消耗的距离 好让子view做出相应的调整 53 | */ 54 | @Override 55 | public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { 56 | //自己实现 57 | } 58 | 59 | /** 60 | * 嵌套滑动的子View在滑动之后,判断父view是否继续处理(也就是父消耗一定距离后,子再消耗,最后判断父消耗不) 61 | * 62 | * @param target 具体嵌套滑动的那个子类 63 | * @param dxConsumed 水平方向嵌套滑动的子View滑动的距离(消耗的距离) 64 | * @param dyConsumed 垂直方向嵌套滑动的子View滑动的距离(消耗的距离) 65 | * @param dxUnconsumed 水平方向嵌套滑动的子View未滑动的距离(未消耗的距离) 66 | * @param dyUnconsumed 垂直方向嵌套滑动的子View未滑动的距离(未消耗的距离) 67 | */ 68 | @Override 69 | public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 70 | super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); 71 | } 72 | 73 | /** 74 | * 嵌套滑动结束 75 | */ 76 | @Override 77 | public void onStopNestedScroll(@NonNull View child) { 78 | mNestedScrollingParentHelper.onStopNestedScroll(child); 79 | } 80 | 81 | /** 82 | * 当子view产生fling滑动时,判断父view是否处拦截fling,如果父View处理了fling,那子view就没有办法处理fling了。 83 | * 84 | * @param target 具体嵌套滑动的那个子类 85 | * @param velocityX 水平方向上的速度 velocityX > 0 向左滑动,反之向右滑动 86 | * @param velocityY 竖直方向上的速度 velocityY > 0 向上滑动,反之向下滑动 87 | * @return 父view是否拦截该fling 88 | */ 89 | @Override 90 | public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { 91 | return super.onNestedPreFling(target, velocityX, velocityY); 92 | } 93 | 94 | 95 | /** 96 | * 当父view不拦截该fling,那么子view会将fling传入父view 97 | * 98 | * @param target 具体嵌套滑动的那个子类 99 | * @param velocityX 水平方向上的速度 velocityX > 0 向左滑动,反之向右滑动 100 | * @param velocityY 竖直方向上的速度 velocityY > 0 向上滑动,反之向下滑动 101 | * @param consumed 子view是否可以消耗该fling,也可以说是子view是否消耗掉了该fling 102 | * @return 父view是否消耗了该fling 103 | */ 104 | @Override 105 | public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { 106 | return super.onNestedFling(target, velocityX, velocityY, consumed); 107 | } 108 | 109 | /** 110 | * 返回当前父view嵌套滑动的方向,分为水平方向与,垂直方法,或者不变 111 | */ 112 | @Override 113 | public int getNestedScrollAxes() { 114 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 115 | } 116 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/view/DependedView.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.view; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.ViewCompat; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.view.ViewConfiguration; 9 | 10 | /** 11 | * Author: andy.xwt 12 | * Date: 2019-07-11 10:42 13 | * Description: 被依赖的TextView,当该view的位置发生改变的时候,那么其他依赖DependedView都会发生改变 14 | */ 15 | 16 | public class DependedView extends View { 17 | 18 | private float mLastX; 19 | private float mLastY; 20 | private final int mDragSlop;//最小的滑动距离 21 | 22 | 23 | public DependedView(Context context) { 24 | this(context, null); 25 | } 26 | 27 | public DependedView(Context context, AttributeSet attrs) { 28 | this(context, attrs, 0); 29 | } 30 | 31 | public DependedView(Context context, AttributeSet attrs, int defStyleAttr) { 32 | super(context, attrs, defStyleAttr); 33 | mDragSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 34 | } 35 | 36 | 37 | @Override 38 | public boolean onTouchEvent(MotionEvent event) { 39 | int action = event.getAction(); 40 | switch (action) { 41 | case MotionEvent.ACTION_DOWN: 42 | mLastX = event.getX(); 43 | mLastY = event.getY(); 44 | break; 45 | 46 | case MotionEvent.ACTION_MOVE: 47 | int dx = (int) (event.getX() - mLastX); 48 | int dy = (int) (event.getY() - mLastY); 49 | if (Math.abs(dx) > mDragSlop || Math.abs(dy) > mDragSlop) { 50 | System.out.println("dx--->" + dx + "dy--->" + dy + "--->" + mDragSlop); 51 | ViewCompat.offsetTopAndBottom(this, dy); 52 | ViewCompat.offsetLeftAndRight(this, dx); 53 | } 54 | mLastX = event.getX(); 55 | mLastY = event.getY(); 56 | break; 57 | 58 | default: 59 | break; 60 | 61 | } 62 | 63 | return true; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/view/NestedScrollingParent2Layout.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.view; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.view.NestedScrollingParent2; 7 | import android.support.v4.view.NestedScrollingParentHelper; 8 | import android.support.v4.view.ViewCompat; 9 | import android.support.v4.view.ViewPager; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.LinearLayout; 14 | 15 | import com.jennifer.andy.nestedscrollingdemo.R; 16 | 17 | /** 18 | * Author: andy.xwt 19 | * Date: 2018/8/8 14:28 20 | * Description:NestedScrolling2机制下的嵌套滑动,实现NestedScrollingParent2接口下,处理fling效果的区别 21 | */ 22 | 23 | public class NestedScrollingParent2Layout extends LinearLayout implements NestedScrollingParent2 { 24 | 25 | private View mTopView; 26 | private View mNavView; 27 | private View mViewPager; 28 | private int mTopViewHeight; 29 | 30 | 31 | private NestedScrollingParentHelper mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 32 | 33 | public NestedScrollingParent2Layout(Context context) { 34 | this(context, null); 35 | } 36 | 37 | public NestedScrollingParent2Layout(Context context, @Nullable AttributeSet attrs) { 38 | this(context, attrs, 0); 39 | } 40 | 41 | public NestedScrollingParent2Layout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 42 | super(context, attrs, defStyleAttr); 43 | setOrientation(VERTICAL); 44 | } 45 | 46 | 47 | @Override 48 | public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) { 49 | return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 50 | } 51 | 52 | 53 | @Override 54 | public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) { 55 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type); 56 | } 57 | 58 | /** 59 | * 在嵌套滑动的子View未滑动之前,判断父view是否优先与子view处理(也就是父view可以先消耗,然后给子view消耗) 60 | * 61 | * @param target 具体嵌套滑动的那个子类 62 | * @param dx 水平方向嵌套滑动的子View想要变化的距离 63 | * @param dy 垂直方向嵌套滑动的子View想要变化的距离 dy<0向下滑动 dy>0 向上滑动 64 | * @param consumed 这个参数要我们在实现这个函数的时候指定,回头告诉子View当前父View消耗的距离 65 | * consumed[0] 水平消耗的距离,consumed[1] 垂直消耗的距离 好让子view做出相应的调整 66 | * @param type 滑动类型,ViewCompat.TYPE_NON_TOUCH fling效果,ViewCompat.TYPE_TOUCH 手势滑动 67 | */ 68 | @Override 69 | public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { 70 | //这里不管手势滚动还是fling都处理 71 | boolean hideTop = dy > 0 && getScrollY() < mTopViewHeight; 72 | boolean showTop = dy < 0 && getScrollY() >= 0 && !target.canScrollVertically(-1); 73 | if (hideTop || showTop) { 74 | scrollBy(0, dy); 75 | consumed[1] = dy; 76 | } 77 | } 78 | 79 | 80 | @Override 81 | public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { 82 | //当子控件处理完后,交给父控件进行处理。 83 | if (dyUnconsumed < 0) {//表示已经向下滑动到头 84 | scrollBy(0, dyUnconsumed); 85 | } 86 | 87 | } 88 | 89 | @Override 90 | public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { 91 | return false; 92 | } 93 | 94 | @Override 95 | public void onStopNestedScroll(@NonNull View target, int type) { 96 | if (type == ViewCompat.TYPE_NON_TOUCH) { 97 | System.out.println("onStopNestedScroll"); 98 | } 99 | 100 | mNestedScrollingParentHelper.onStopNestedScroll(target, type); 101 | } 102 | 103 | 104 | @Override 105 | public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { 106 | return false; 107 | } 108 | 109 | @Override 110 | public int getNestedScrollAxes() { 111 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 112 | } 113 | 114 | @Override 115 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 116 | //ViewPager修改后的高度= 总高度-导航栏高度 117 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 118 | ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams(); 119 | layoutParams.height = getMeasuredHeight() - mNavView.getMeasuredHeight(); 120 | mViewPager.setLayoutParams(layoutParams); 121 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 122 | } 123 | 124 | @Override 125 | protected void onFinishInflate() { 126 | super.onFinishInflate(); 127 | mTopView = findViewById(R.id.iv_head_image); 128 | mNavView = findViewById(R.id.tab_layout); 129 | mViewPager = findViewById(R.id.view_pager); 130 | if (!(mViewPager instanceof ViewPager)) { 131 | throw new RuntimeException("id view_pager should be viewpager!"); 132 | } 133 | } 134 | 135 | @Override 136 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 137 | super.onSizeChanged(w, h, oldw, oldh); 138 | mTopViewHeight = mTopView.getMeasuredHeight(); 139 | } 140 | 141 | @Override 142 | public void scrollTo(int x, int y) { 143 | if (y < 0) { 144 | y = 0; 145 | } 146 | if (y > mTopViewHeight) { 147 | y = mTopViewHeight; 148 | } 149 | super.scrollTo(x, y); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/view/NestedScrollingParentLayout.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.view; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.content.Context; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.view.NestedScrollingParent; 8 | import android.support.v4.view.NestedScrollingParentHelper; 9 | import android.support.v4.view.ViewCompat; 10 | import android.support.v4.view.ViewPager; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.view.animation.DecelerateInterpolator; 15 | import android.widget.LinearLayout; 16 | 17 | import com.jennifer.andy.nestedscrollingdemo.R; 18 | 19 | /** 20 | * Author: andy.xwt 21 | * Date: 2018/8/8 14:28 22 | * Description:NestedScrolling机制下的嵌套滑动 实现NestedScrollingParent接口 23 | */ 24 | 25 | public class NestedScrollingParentLayout extends LinearLayout implements NestedScrollingParent { 26 | 27 | private View mTopView; 28 | private View mNavView; 29 | private View mViewPager; 30 | private int mTopViewHeight; 31 | private ValueAnimator mValueAnimator; 32 | 33 | private NestedScrollingParentHelper mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 34 | 35 | public NestedScrollingParentLayout(Context context) { 36 | this(context, null); 37 | } 38 | 39 | public NestedScrollingParentLayout(Context context, @Nullable AttributeSet attrs) { 40 | this(context, attrs, 0); 41 | } 42 | 43 | public NestedScrollingParentLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 44 | super(context, attrs, defStyleAttr); 45 | setOrientation(VERTICAL); 46 | } 47 | 48 | 49 | @Override 50 | public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) { 51 | return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 52 | } 53 | 54 | 55 | @Override 56 | public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) { 57 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); 58 | } 59 | 60 | /** 61 | * 在嵌套滑动的子View未滑动之前,判断父view是否优先与子view处理(也就是父view可以先消耗,然后给子view消耗) 62 | * 63 | * @param target 具体嵌套滑动的那个子类 64 | * @param dx 水平方向嵌套滑动的子View想要变化的距离 65 | * @param dy 垂直方向嵌套滑动的子View想要变化的距离 dy<0向下滑动 dy>0 向上滑动 66 | * @param consumed 这个参数要我们在实现这个函数的时候指定,回头告诉子View当前父View消耗的距离 67 | * consumed[0] 水平消耗的距离,consumed[1] 垂直消耗的距离 好让子view做出相应的调整 68 | */ 69 | @Override 70 | public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { 71 | boolean hideTop = dy > 0 && getScrollY() < mTopViewHeight; 72 | boolean showTop = dy < 0 && getScrollY() >= 0 && !target.canScrollVertically(-1); 73 | if (hideTop || showTop) { 74 | scrollBy(0, dy); 75 | consumed[1] = dy; 76 | } 77 | } 78 | 79 | 80 | @Override 81 | public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 82 | 83 | } 84 | 85 | @Override 86 | public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { 87 | return false; 88 | } 89 | 90 | @Override 91 | public void onStopNestedScroll(@NonNull View target) { 92 | mNestedScrollingParentHelper.onStopNestedScroll(target); 93 | } 94 | 95 | 96 | @Override 97 | public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { 98 | final int distance = Math.abs(getScrollY()); 99 | final int duration; 100 | if (velocityY > 0) {//向上滑 101 | duration = 3 * Math.round(1000 * (distance / velocityY)); 102 | startAnimation(duration, getScrollY(), mTopViewHeight); 103 | } else if (velocityY < 0) {//向下滑动 104 | final float distanceRatio = (float) distance / getHeight(); 105 | duration = (int) ((distanceRatio + 1) * 150); 106 | startAnimation(duration, getScrollY(), 0); 107 | } 108 | 109 | return true; 110 | } 111 | 112 | @Override 113 | public int getNestedScrollAxes() { 114 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 115 | } 116 | 117 | @Override 118 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 119 | //ViewPager修改后的高度= 总高度-导航栏高度 120 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 121 | ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams(); 122 | layoutParams.height = getMeasuredHeight() - mNavView.getMeasuredHeight(); 123 | mViewPager.setLayoutParams(layoutParams); 124 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 125 | } 126 | 127 | @Override 128 | protected void onFinishInflate() { 129 | super.onFinishInflate(); 130 | mTopView = findViewById(R.id.iv_head_image); 131 | mNavView = findViewById(R.id.tab_layout); 132 | mViewPager = findViewById(R.id.view_pager); 133 | if (!(mViewPager instanceof ViewPager)) { 134 | throw new RuntimeException("id view_pager should be viewpager!"); 135 | } 136 | } 137 | 138 | @Override 139 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 140 | super.onSizeChanged(w, h, oldw, oldh); 141 | mTopViewHeight = mTopView.getMeasuredHeight(); 142 | } 143 | 144 | @Override 145 | public void scrollTo(int x, int y) { 146 | if (y < 0) { 147 | y = 0; 148 | } 149 | if (y > mTopViewHeight) { 150 | y = mTopViewHeight; 151 | } 152 | super.scrollTo(x, y); 153 | } 154 | 155 | private void startAnimation(long duration, int startY, int endY) { 156 | if (mValueAnimator == null) { 157 | mValueAnimator = new ValueAnimator(); 158 | mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 159 | @Override 160 | public void onAnimationUpdate(ValueAnimator animation) { 161 | int animatedValue = (int) animation.getAnimatedValue(); 162 | scrollTo(0, animatedValue); 163 | } 164 | }); 165 | } else { 166 | mValueAnimator.cancel(); 167 | } 168 | mValueAnimator.setInterpolator(new DecelerateInterpolator()); 169 | mValueAnimator.setIntValues(startY, endY); 170 | mValueAnimator.setDuration(duration); 171 | mValueAnimator.start(); 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/view/NestedTraditionLayout.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.view; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.view.ViewPager; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewConfiguration; 11 | import android.view.ViewGroup; 12 | import android.widget.LinearLayout; 13 | 14 | import com.jennifer.andy.nestedscrollingdemo.R; 15 | 16 | /** 17 | * Author: andy.xwt 18 | * Date: 2018/8/8 14:27 19 | * Description: 20 | * 传统处理嵌套滑动的方式,如果父控件拦截,根据传统事件分发机制,如果父控件确定拦截事件,那么在同一事件序列中,子控件是没有办法获取到事件, 21 | * 在下面的例子中,如果是同一事件序列中滑动导致headView隐藏,那么除非手指抬起,不然子控件是不能响应事件的。 22 | */ 23 | 24 | public class NestedTraditionLayout extends LinearLayout { 25 | 26 | private View mHeadView; 27 | private View mNavView; 28 | private ViewPager mViewPager; 29 | 30 | private int mHeadTopHeight; 31 | private int mLastY; 32 | private boolean isHeadHide; 33 | 34 | private final String TAG = "NestedTraditionLayout"; 35 | 36 | public NestedTraditionLayout(Context context) { 37 | this(context, null); 38 | } 39 | 40 | public NestedTraditionLayout(Context context, @Nullable AttributeSet attrs) { 41 | this(context, attrs, 0); 42 | } 43 | 44 | public NestedTraditionLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 45 | super(context, attrs, defStyleAttr); 46 | } 47 | 48 | @Override 49 | public boolean onInterceptTouchEvent(MotionEvent event) { 50 | int action = event.getAction() & MotionEvent.ACTION_MASK; 51 | int y = (int) event.getY(); 52 | switch (action) { 53 | case MotionEvent.ACTION_DOWN: 54 | mLastY = y; 55 | break; 56 | case MotionEvent.ACTION_MOVE: 57 | int dy = mLastY - y; 58 | //如果父控件拦截,根据传统事件传递机制,如果父控件确定拦截事件,那么在同一事件序列中,子控件是没有办法获取到事件的。 59 | if (Math.abs(dy) > ViewConfiguration.getTouchSlop()) { 60 | if (dy > 0 && !isHeadHide) { //如果是向上滑,且当前headView没有隐藏,那么就拦截 61 | Log.d(TAG, "onInterceptTouchEvent: 开始向上拦截"); 62 | return true; 63 | } else if (dy < 0 && isHeadHide) {//如果是向下, 且将headView已经隐藏,那么就拦截 64 | Log.d(TAG, "onInterceptTouchEvent: 开始向下拦截"); 65 | return true; 66 | } 67 | } 68 | break; 69 | } 70 | return super.onInterceptTouchEvent(event);//不拦截事件,把事件让给子控件。 71 | 72 | } 73 | 74 | @Override 75 | public boolean onTouchEvent(MotionEvent event) { 76 | 77 | int action = event.getAction() & MotionEvent.ACTION_MASK; 78 | int y = (int) event.getY(); 79 | 80 | switch (action) { 81 | case MotionEvent.ACTION_DOWN: 82 | mLastY = y; 83 | break; 84 | case MotionEvent.ACTION_MOVE: 85 | int dy = mLastY - y; 86 | if (Math.abs(dy) > ViewConfiguration.getTouchSlop()) { 87 | scrollBy(0, dy); 88 | } 89 | mLastY = y; 90 | break; 91 | } 92 | return super.onTouchEvent(event); 93 | } 94 | 95 | /** 96 | * 重新scrollTo方法,因为scrollBy最终会调用,scrollTo方法 97 | */ 98 | @Override 99 | public void scrollTo(int x, int y) { 100 | if (y < 0) { 101 | y = 0; 102 | } 103 | if (y > mHeadTopHeight) { 104 | y = mHeadTopHeight; 105 | } 106 | super.scrollTo(x, y); 107 | isHeadHide = getScrollY() == mHeadTopHeight;//判断当前head是否已经隐藏了 108 | } 109 | 110 | @Override 111 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 112 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 113 | //ViewPager修改后的高度= 总高度-导航栏高度 114 | ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams(); 115 | layoutParams.height = getMeasuredHeight() - mNavView.getMeasuredHeight(); 116 | mViewPager.setLayoutParams(layoutParams); 117 | //当ViewPager修改高度后,重新开始测量 118 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 119 | 120 | } 121 | 122 | @Override 123 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 124 | super.onSizeChanged(w, h, oldw, oldh); 125 | mHeadTopHeight = mHeadView.getMeasuredHeight();//获取headView高度 126 | } 127 | 128 | @Override 129 | protected void onFinishInflate() { 130 | super.onFinishInflate(); 131 | mHeadView = findViewById(R.id.iv_head_image); 132 | mNavView = findViewById(R.id.tab_layout); 133 | mViewPager = findViewById(R.id.view_pager); 134 | if (!(mViewPager instanceof ViewPager)) { 135 | throw new RuntimeException("id view_pager should be viewpager!"); 136 | } 137 | } 138 | 139 | 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/com/jennifer/andy/nestedscrollingdemo/view/StickyNavLayout.java: -------------------------------------------------------------------------------- 1 | package com.jennifer.andy.nestedscrollingdemo.view; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.view.NestedScrollingParent2; 7 | import android.support.v4.view.NestedScrollingParentHelper; 8 | import android.support.v4.view.ViewCompat; 9 | import android.support.v4.view.ViewPager; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.LinearLayout; 14 | 15 | import com.jennifer.andy.nestedscrollingdemo.R; 16 | 17 | /** 18 | * Author: andy.xwt 19 | * Date: 2019-07-08 22:11 20 | * Description: 21 | */ 22 | 23 | public class StickyNavLayout extends LinearLayout implements NestedScrollingParent2 { 24 | 25 | private NestedScrollingParentHelper mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 26 | private View mTopView;//头部view 27 | private View mNavView;//导航view 28 | private ViewPager mViewPager;//Viewpager 29 | private ScrollChangeListener mScrollChangeListener; 30 | /** 31 | * 父控件可以滚动的距离 32 | */ 33 | private float mCanScrollDistance = 0f; 34 | 35 | public StickyNavLayout(Context context) { 36 | this(context, null); 37 | } 38 | 39 | public StickyNavLayout(Context context, @Nullable AttributeSet attrs) { 40 | this(context, attrs, 0); 41 | } 42 | 43 | public StickyNavLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 44 | super(context, attrs, defStyleAttr); 45 | setOrientation(LinearLayout.VERTICAL); 46 | } 47 | 48 | /** 49 | * 父控件接受嵌套滑动,不管是手势滑动还是fling 父控件都接受 50 | */ 51 | @Override 52 | public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) { 53 | return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 54 | } 55 | 56 | @Override 57 | public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) { 58 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type); 59 | } 60 | 61 | 62 | @Override 63 | public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { 64 | //如果子view欲向上滑动,则先交给父view滑动 65 | boolean hideTop = dy > 0 && getScrollY() < mCanScrollDistance; 66 | //如果子view欲向下滑动,必须要子view不能向下滑动后,才能交给父view滑动 67 | boolean showTop = dy < 0 && getScrollY() >= 0 && !target.canScrollVertically(-1); 68 | if (hideTop || showTop) { 69 | scrollBy(0, dy); 70 | consumed[1] = dy;// consumed[0] 水平消耗的距离,consumed[1] 垂直消耗的距离 71 | } 72 | } 73 | 74 | 75 | @Override 76 | public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { 77 | if (dyUnconsumed < 0) {//表示已经向下滑动到头,这里不用区分手势还是fling 78 | scrollBy(0, dyUnconsumed); 79 | } 80 | } 81 | 82 | @Override 83 | public void onStopNestedScroll(@NonNull View target, int type) { 84 | mNestedScrollingParentHelper.onStopNestedScroll(target, type); 85 | } 86 | 87 | /** 88 | * 嵌套滑动时,如果父View处理了fling,那子view就没有办法处理fling了,所以这里要返回为false 89 | */ 90 | @Override 91 | public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { 92 | return false; 93 | } 94 | 95 | @Override 96 | public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { 97 | return false; 98 | } 99 | 100 | @Override 101 | protected void onFinishInflate() { 102 | super.onFinishInflate(); 103 | mTopView = findViewById(R.id.sl_top_view); 104 | mNavView = findViewById(R.id.sl_tab); 105 | mViewPager = findViewById(R.id.sl_viewpager); 106 | 107 | } 108 | 109 | @Override 110 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 111 | //先测量一次 112 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 113 | //ViewPager修改后的高度= 总高度-TabLayout高度 114 | ViewGroup.LayoutParams lp = mViewPager.getLayoutParams(); 115 | lp.height = getMeasuredHeight() - mNavView.getMeasuredHeight(); 116 | mViewPager.setLayoutParams(lp); 117 | //因为ViewPager修改了高度,所以需要重新测量 118 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 119 | } 120 | 121 | 122 | @Override 123 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 124 | mCanScrollDistance = mTopView.getMeasuredHeight() - getResources().getDimension(R.dimen.normal_title_height); 125 | } 126 | 127 | @Override 128 | public void scrollTo(int x, int y) { 129 | if (y < 0) { 130 | y = 0; 131 | } 132 | if (y > mCanScrollDistance) { 133 | y = (int) mCanScrollDistance; 134 | } 135 | if (mScrollChangeListener != null) { 136 | mScrollChangeListener.onScroll(y / mCanScrollDistance); 137 | } 138 | if (getScrollY() != y) super.scrollTo(x, y); 139 | } 140 | 141 | 142 | @Override 143 | public int getNestedScrollAxes() { 144 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 145 | } 146 | 147 | 148 | public interface ScrollChangeListener { 149 | /** 150 | * 移动监听 151 | * 152 | * @param moveRatio 移动比例 153 | */ 154 | void onScroll(float moveRatio); 155 | } 156 | 157 | public void setScrollChangeListener(ScrollChangeListener scrollChangeListener) { 158 | mScrollChangeListener = scrollChangeListener; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /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/ic_action_back_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndyJennifer/NestedScrollingDemo/ebbc2b21417f4177c4d71c3f793ebcd129d83f3c/app/src/main/res/drawable/ic_action_back_black.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_back_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndyJennifer/NestedScrollingDemo/ebbc2b21417f4177c4d71c3f793ebcd129d83f3c/app/src/main/res/drawable/ic_action_back_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_cdl_abl_ctl.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 14 | 20 | 21 | 29 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_cdl_demo1.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 17 | 18 | 23 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_cdl_demo2.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 18 | 19 | 20 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_cdl_demo3.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 16 | 17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_cdl_demo4.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 17 | 18 | 19 | 24 | 25 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_cdl_with_appbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 19 | 20 | 21 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_coord_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 |