高級UI<第四十七篇>:NestedScrolling擴展(添加頭和尾)

在上一篇文章中琳猫,我使用NestedScrollingParentNestedScrollingChild并結(jié)合輔助類NestedScrollingParentHelperNestedScrollingChildHelper實現(xiàn)了以下效果:

49.gif

UI界面由三個部分組成,分別是圖片、標題箕戳、主體撩银,但是一般在現(xiàn)實需求中淳地,標題默認是最上面的著拭,那么,有沒有辦法讓界面默認顯示標題和主體衡怀,當下滑時才會顯示圖片棍矛,圖片作為頭部,順便添加下尾部抛杨。

答案是必須有够委,下面開始分步驟說明。

首先看一下布局實現(xiàn)

<?xml version="1.0" encoding="utf-8"?>
<com.zyc.hezuo.aaademo.MyCustomNestedScrollingParent
    android:id="@+id/nestedparent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:textSize="20sp"
        android:gravity="center"
        android:background="#E9A8E9"
        android:text="我是標題"/>


    <com.zyc.hezuo.aaademo.MyCustomNestedScrollingChild
        android:id="@+id/nestedchild"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">


        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#FFFFFF"
            android:text="意內(nèi)容我是任容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容意內(nèi)容我是任容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容"
            android:textSize="26sp"/>

    </com.zyc.hezuo.aaademo.MyCustomNestedScrollingChild>

</com.zyc.hezuo.aaademo.MyCustomNestedScrollingParent>

使用自定義NestedScrollingParent和自定義NestedScrollingChild完成嵌套布局怖现,這兩個類的完整代碼稍后貼出茁帽。

該布局的效果如下:

圖片.png

然后貼出自定義NestedScrollingChild的實現(xiàn)代碼

public class MyCustomNestedScrollingChild extends LinearLayout implements NestedScrollingChild {

    private NestedScrollingChildHelper mNestedScrollingChildHelper;
    private final int[] offset = new int[2]; //偏移量
    private final int[] consumed = new int[2]; //消費
    private int lastY;

    public MyCustomNestedScrollingChild(Context context) {
        super(context);
    }

    public MyCustomNestedScrollingChild(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyCustomNestedScrollingChild(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //記錄觸摸時的Y軸方向
                lastY = (int) event.getRawY();
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                break;
            case MotionEvent.ACTION_MOVE:
                int y = (int) (event.getRawY());
                int dy = y - lastY;//dy為屏幕上滑動的偏移量
                lastY = y;
                dispatchNestedPreScroll(0, dy, consumed, offset);

                break;
        }

        return true;
    }

    //初始化helper對象
    private NestedScrollingChildHelper getScrollingChildHelper() {
        if (mNestedScrollingChildHelper == null) {
            mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
            mNestedScrollingChildHelper.setNestedScrollingEnabled(true);
        }
        return mNestedScrollingChildHelper;
    }

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {        //設(shè)置滾動事件可用性
        getScrollingChildHelper().setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {//是否可以滾動
        return getScrollingChildHelper().isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes) {//開始滾動
        Log.d("yunchong", "child ---- startNestedScroll");

        return getScrollingChildHelper().startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {//停止?jié)L動,清空滾動狀態(tài)
        getScrollingChildHelper().stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {//判斷是否含有對應(yīng)的NestedScrollingParent
        return getScrollingChildHelper().hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
        //在子view進行滾動之后調(diào)用此方法屈嗤,詢問父view是否還要進行余下(unconsumed)的滾動脐雪。
        //前四個參數(shù)為輸入?yún)?shù),用于告訴父view已經(jīng)消費和尚未消費的距離恢共,最后一個參數(shù)為輸出參數(shù),用于子view獲取父view位置的偏移量璧亚。
        //如果父view接收了它的滾動參數(shù)讨韭,進行了部分消費,則這個函數(shù)返回true癣蟋,否則為false
        return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        //在子view自己進行滾動之前調(diào)用此方法透硝,詢問父view是否要在子view之前進行滾動。
        //此方法的前兩個參數(shù)用于告訴父View此次要滾動的距離疯搅;而第三第四個參數(shù)用于子view獲取父view消費掉的距離和父view位置的偏移量濒生。
        return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
    }

}

這段代碼的核心都在onTouchEvent這個方法里,dispatchNestedPreScroll方法將滑動偏移量傳遞給父view幔欧,startNestedScroll確定父view是否會配合子view實現(xiàn)滑動罪治。

最后丽声,開始來自定義NestedScrollingParent,代碼如下:

public class MyCustomNestedScrollingParent extends LinearLayout implements NestedScrollingParent {

    //頭部圖片最大高度
    private static final int MAX_HEIGHT = 500;

    //頭
    private ImageView headerView;
    //尾
    private View footerView;
    //目標view
    private View childView;


    public MyCustomNestedScrollingParent(Context context) {
        this(context, null);
    }

    public MyCustomNestedScrollingParent(Context context, AttributeSet attrs) {
        super(context, attrs);
        //默認方向為垂直
        setOrientation(LinearLayout.VERTICAL);

        //頭
        headerView = new ImageView(context);
        headerView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        headerView.setImageDrawable(getResources().getDrawable(R.mipmap.demo));

        //尾
        footerView = LayoutInflater.from(getContext()).inflate(R.layout.footer_layout, null, false);
    }


    //當前視圖完全加載完畢觉义,顯示在屏幕上后執(zhí)行
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        //找出目標view
        for(int index = 0;index < getChildCount();index ++){
            if(getChildAt(index) instanceof MyCustomNestedScrollingChild){
                childView = getChildAt(index);
            }
        }

        LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, MAX_HEIGHT);
        //添加頭
        addView(headerView, 0, layoutParams);
        //添加尾
        addView(footerView, getChildCount());
        // 上移雁社,即隱藏header
        scrollBy(0, MAX_HEIGHT);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ViewGroup.LayoutParams params = childView.getLayoutParams();
        //標題高度
        int titleHeight = (int) (50 * getContext().getResources().getDisplayMetrics().density + 0.5);
        params.height = getMeasuredHeight() - titleHeight;

    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        //如果當前觸摸的view是MyCustomNestedScrollingChild則返回true
        if (target instanceof MyCustomNestedScrollingChild) {
            return true;
        }
        return false;
    }

    @Override
    public void onStopNestedScroll(View target) {

    }

    //先于child滾動
    //前3個為輸入?yún)?shù),最后一個是輸出參數(shù)
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        // 如果在自定義ViewGroup之上還有父View交給我來處理
        getParent().requestDisallowInterceptTouchEvent(true);

        //滑動父view
        scrollBy(0, -dy);

        consumed[1] = dy;
    }

    //后于child滾動
    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        //是否消費了手指滑動事件
        return false;
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        //是否消費了手指滑動事件
        return false;
    }

    /**
     * 限制滑動 移動y軸不能超出最大范圍
     */
    @Override
    public void scrollTo(int x, int y) {

        footerView.getLayoutParams().height = (int) (100 * getContext().getResources().getDisplayMetrics().density + 0.5);

        if (y < 0) {
            y = 0;
        } else if (y > MAX_HEIGHT + footerView.getHeight()) {
            y = MAX_HEIGHT + footerView.getHeight();
        }
        super.scrollTo(x, y);
    }

}

最終效果如下:

50.gif

MyCustomNestedScrollingParent的實現(xiàn)我大致說明一下:

(1)當屏幕中的MyCustomNestedScrollingParent加載完成時(包括所有的子視圖)晒骇,會執(zhí)行onFinishInflate方法霉撵,所以,在這個方法里動態(tài)添加頭和尾最為合適洪囤,計算頭布局的高度(假設(shè)為y)徒坡,最后將MyCustomNestedScrollingParent向上滑動距離為y的偏移量。代碼如下:

//當前視圖完全加載完畢瘤缩,顯示在屏幕上后執(zhí)行
@Override
protected void onFinishInflate() {
    super.onFinishInflate();

    //找出目標view
    for(int index = 0;index < getChildCount();index ++){
        if(getChildAt(index) instanceof MyCustomNestedScrollingChild){
            childView = getChildAt(index);
        }
    }

    LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, MAX_HEIGHT);
    //添加頭
    addView(headerView, 0, layoutParams);
    //添加尾
    addView(footerView, getChildCount());
    // 上移喇完,即隱藏header
    scrollBy(0, MAX_HEIGHT);

}

但是,scrollBy之后款咖,主題布局的高度不會變化何暮,所以,必須要在scrollBy之前將提前修改主體布局的高度铐殃。在添加頭和尾時海洼,使用了addView方法,查看源碼之后發(fā)現(xiàn)富腊,一旦執(zhí)行這個方法之后布局就會重新測量坏逢,執(zhí)行onMeasure方法,所以赘被,完全可以在這個方法里調(diào)整主體布局的高度是整,如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    ViewGroup.LayoutParams params = childView.getLayoutParams();
    //標題高度
    int titleHeight = (int) (50 * getContext().getResources().getDisplayMetrics().density + 0.5);
    params.height = getMeasuredHeight() - titleHeight;

}

在onStartNestedScroll方法中給出返回值,只有當目標view是MyCustomNestedScrollingChild才允許被滑動民假,代碼如下:

@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
    //如果當前觸摸的view是MyCustomNestedScrollingChild則返回true
    if (target instanceof MyCustomNestedScrollingChild) {
        return true;
    }
    return false;
}

當手指滑動主題布局時浮入,父view也會跟著一起滑動,此時羊异,自定義MyCustomNestedScrollingParent的onNestedPreScroll方法會不斷被執(zhí)行事秀,代碼如下:

//前3個為輸入?yún)?shù),最后一個是輸出參數(shù)
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
    // 如果在自定義ViewGroup之上還有父View交給我來處理
    getParent().requestDisallowInterceptTouchEvent(true);
    //滑動父view
    scrollBy(0, -dy);

    consumed[1] = dy;
}

最后野舶,為了控制滑動范圍易迹,需要重寫scrollTo方法,具體實現(xiàn)如下:

/**
 * 限制滑動 移動y軸不能超出最大范圍
 */
@Override
public void scrollTo(int x, int y) {

    footerView.getLayoutParams().height = (int) (100 * getContext().getResources().getDisplayMetrics().density + 0.5);

    if (y < 0) {
        y = 0;
    } else if (y > MAX_HEIGHT + footerView.getHeight()) {
        y = MAX_HEIGHT + footerView.getHeight();
    }
    super.scrollTo(x, y);
}
下篇預(yù)知

以上案例中并沒有發(fā)生Fling事件平道,因為在子view中并沒有將Fling的速度值傳遞給父view睹欲,下一篇將實現(xiàn)Fling事件。

[本章完...]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窘疮,隨后出現(xiàn)的幾起案子袋哼,更是在濱河造成了極大的恐慌,老刑警劉巖考余,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件先嬉,死亡現(xiàn)場離奇詭異,居然都是意外死亡楚堤,警方通過查閱死者的電腦和手機疫蔓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來身冬,“玉大人衅胀,你說我怎么就攤上這事∷煮荩” “怎么了滚躯?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嘿歌。 經(jīng)常有香客問我掸掏,道長,這世上最難降的妖魔是什么宙帝? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任丧凤,我火速辦了婚禮,結(jié)果婚禮上步脓,老公的妹妹穿的比我還像新娘愿待。我一直安慰自己,他們只是感情好靴患,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布仍侥。 她就那樣靜靜地躺著,像睡著了一般鸳君。 火紅的嫁衣襯著肌膚如雪农渊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天或颊,我揣著相機與錄音腿时,去河邊找鬼。 笑死饭宾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的格了。 我是一名探鬼主播看铆,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盛末!你這毒婦竟也來了弹惦?” 一聲冷哼從身側(cè)響起否淤,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棠隐,沒想到半個月后石抡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡助泽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年啰扛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗡贺。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡隐解,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诫睬,到底是詐尸還是另有隱情煞茫,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布摄凡,位于F島的核電站续徽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏亲澡。R本人自食惡果不足惜钦扭,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谷扣。 院中可真熱鬧土全,春花似錦、人聲如沸会涎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽末秃。三九已至概页,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間练慕,已是汗流浹背惰匙。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铃将,地道東北人项鬼。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像劲阎,于是被迫代替她去往敵國和親绘盟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354