這篇文章會(huì)用NestedScrolling機(jī)制做一個(gè)實(shí)例,此實(shí)例代碼參考自:http://blog.csdn.net/al4fun/article/details/53889075
我在源代碼的基礎(chǔ)上风瘦,刪減了些代碼剃根,加了些注釋
例子效果如下:1.gif
先貼出布局文件代碼:
<?xml version="1.0" encoding="utf-8"?>
<com.example.nesteddemo.MyParent xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.nesteddemo.MainActivity"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round"
android:layout_gravity="center_horizontal"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="我是不會(huì)隱藏的文字欄,辣雞"
android:background="#FFB6C1"
android:textSize="25sp"/>
<com.example.nesteddemo.MyChild
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/libai"
android:textSize="25sp"/>
</com.example.nesteddemo.MyChild>
</com.example.nesteddemo.MyParent>
布局文件很簡(jiǎn)單舒憾,其中MyParent和MyChild分別是實(shí)現(xiàn)了NestedScrollingParent和NestedScrollingChild接口的父容器和子元素,這兩個(gè)View都繼承自LinearLayout。
我們先來看看MyChild是怎樣實(shí)現(xiàn)的:
public class MyChild extends LinearLayout implements NestedScrollingChild {
NestedScrollingChildHelper nscp;
int lastY;
//這兩個(gè)數(shù)組用來接收父容器傳過來的參數(shù)
int[] consumed;
int[] offsetWindow;
int showHeight;
public MyChild(Context context) {
this(context,null);
}
public MyChild(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//第一次測(cè)量香浩,因?yàn)槭莣rap_content,測(cè)量出來的只是父容器除了ImageView和TextView剩余的高度
//此次測(cè)量只是為了求得剩余的高度
//如果沒有第二次測(cè)量,那么下面的文字就會(huì)顯示不出來
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
showHeight = getMeasuredHeight();
//現(xiàn)在我們把MeasureSpec設(shè)置為UNSPECIFIED,這樣MyChild的高度就沒有限制了臼勉,也就能顯示全部的文字了
heightMeasureSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int y = (int) event.getRawY();
int dy = y-lastY;
lastY = y;
//開啟NestedScrolling機(jī)制邻吭,如果找到了匹配的父容器,那么就與父容器配合消費(fèi)掉滑動(dòng)距離
if(startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)){
//dy是我們傳過去的滑動(dòng)的距離囱晴,父容器可以根據(jù)邏輯來選擇要不要消費(fèi)膏蚓,消費(fèi)多少
dispatchNestedPreScroll(0,dy,consumed,offsetWindow);
scrollBy(0,-dy);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
//scrollBy內(nèi)部調(diào)用scrollTo,我們不能滑出去,也不能滑的太下面畸写,我們要修正這些情況
@Override
public void scrollTo(@Px int x, @Px int y) {
int maxY = getMeasuredHeight()-showHeight;
if(y>maxY){
y=maxY;
}
else if(y<0){
y=0;
}
super.scrollTo(x, y);
}
//這里使用單例模式提供Helper驮瞧,我發(fā)現(xiàn)如果沒有單例模式,機(jī)制就會(huì)失效
//原因我大致的猜到了枯芬,但是我還不能具體的表達(dá)出來论笔,如果有人知道,請(qǐng)?jiān)谠u(píng)論區(qū)留下言
private NestedScrollingChildHelper getNscp(){
if(nscp == null){
nscp = new NestedScrollingChildHelper(this);
nscp.setNestedScrollingEnabled(true);
return nscp;
}else {
return nscp;
}
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
getNscp().setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return getNscp().isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return getNscp().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
getNscp().stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return getNscp().hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
return getNscp().dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return getNscp().dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return getNscp().dispatchNestedFling(velocityX,velocityY,consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return getNscp().dispatchNestedPreFling(velocityX,velocityY);
}
}
大多數(shù)的函數(shù)我們都用幫助類的同名函數(shù)處理了千所,其余的代碼我已經(jīng)寫上了詳細(xì)的注釋狂魔,不難看懂。
接下看下MyParent的實(shí)現(xiàn):
public class MyParent extends LinearLayout implements NestedScrollingParent {
NestedScrollingParentHelper nsp;
ImageView iv;
MyChild nsc;
int ivHeight;
public MyParent(Context context) {
this(context,null);
}
public MyParent(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
//創(chuàng)建一個(gè)Helper類
nsp = new NestedScrollingParentHelper(this);
}
//拿到父容器里面的三個(gè)子View
@Override
protected void onFinishInflate() {
super.onFinishInflate();
iv = (ImageView) getChildAt(0);
nsc = (MyChild) getChildAt(2);
//拿到ImageView的高度
iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if(ivHeight<=0){
ivHeight = iv.getMeasuredHeight();
}
}
});
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
//參數(shù)里target是實(shí)現(xiàn)了NestedScrolling機(jī)制的子元素淫痰,這個(gè)子元素可以不是父容器的直接子元素
//child是包含了target的View最楷,這個(gè)View是父容器的直接子元素
if(target instanceof MyChild){
return true;
}
return false;
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
//dy是子View傳過來的,來詢問父容器是不是要消費(fèi)他待错,要的話籽孙,就把dy放進(jìn)consumed數(shù)組,表示我消費(fèi)了
//其中consumed數(shù)組,consumed[0]表示x方向的距離火俄,consumed[1]表示y方向的距離
if(showImg(dy)||hideImg(dy)/*這里根據(jù)業(yè)務(wù)邏輯來判斷*/){
scrollBy(0,-dy);
consumed[1] = dy;
}
}
private boolean hideImg(int dy) {
//上拉的時(shí)候犯建,判斷是不是要隱藏圖片
if(dy<0){
if(getScrollY()<ivHeight){
//判斷只要上移的部分,沒有超過ImageView烛占,那么就讓父容器繼續(xù)滑動(dòng)
return true;
}
}
return false;
}
private boolean showImg(int dy) {
//下拉的時(shí)候胎挎,判斷是不是要顯示圖片
if(dy>0){
if(nsc.getScrollY()==0){
return true;
}
}
return false;
}
//scrollBy內(nèi)部調(diào)用scrollTo,我們父容器不能滑出去,也不能滑的太下面忆家,我們要修正這些情況
@Override
public void scrollTo(@Px int x, @Px int y) {
if(y>ivHeight){
y = ivHeight;
}
else if(y<0){
y=0;
}
super.scrollTo(x,y);
}
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
nsp.onNestedScrollAccepted(child,target,nestedScrollAxes);
}
@Override
public void onStopNestedScroll(View target) {
nsp.onStopNestedScroll(target);
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
@Override
public int getNestedScrollAxes() {
return 0;
}
}
和MyChild一樣犹菇,大多數(shù)函數(shù)都用幫助類的同名函數(shù)處理了,其余的也寫上了詳細(xì)的注釋芽卿。
好了揭芍,沒了。
完整代碼地址:https://github.com/ChenTianSaber/NestedScrollingDemo
結(jié)束:
這篇文章先到此結(jié)束卸例。
感覺寫的好干称杨,因?yàn)榇蠖鄶?shù)地方都被幫助類實(shí)現(xiàn)了,而且例子也簡(jiǎn)單筷转,沒有什么一步一步的步驟姑原。大家可以看懂了之后,自己寫一遍呜舒。
接下來我們會(huì)分析一下NestedScrolling的源碼锭汛,來看看它們是怎么做到相互配合的。