自定義behavior 仿支付寶首頁(yè)

相信大家平時(shí)使用最多的應(yīng)用應(yīng)該就是支付寶和微信了吧秩霍,但是個(gè)人感覺(jué)從設(shè)計(jì)上看 支付寶的交互設(shè)計(jì)明顯更勝一籌,要說(shuō)到底是哪里強(qiáng)吧,我感覺(jué)就是支付寶的里面各種嵌套滑動(dòng)給了應(yīng)用更高的可玩性或链,不會(huì)顯得那么單調(diào).
于是,咱們就擼個(gè)支付寶首頁(yè)玩吧理澎! 哈哈哈

先擺上項(xiàng)目地址https://github.com/nokiafen/viewpro/tree/master/alihomepage

獻(xiàn)上動(dòng)圖

ali_sc_shot.gif

是不是覺(jué)得我山寨了一個(gè)支付寶逞力??糠爬? 冤枉寇荧!我只是偷了懶只實(shí)現(xiàn)了交互效果而已 其它區(qū)域都是截圖啊 ,大兄弟执隧!

怎么實(shí)現(xiàn)呢揩抡?其實(shí)只實(shí)現(xiàn)交互效果并不算太復(fù)雜,改bug倒是花了不少功夫 就一個(gè)類300來(lái)行就可以搞定了 不信看我截圖

image.png

要達(dá)到這個(gè)效果最簡(jiǎn)單的就是自定義CoordinatorLayout.Behavior了 大概談?wù)勊墓ぷ髟戆啥屏穑珻oordinatorLayout就是一個(gè)加強(qiáng)版的FrameLayout,它通過(guò)Behavior來(lái)與它的直接子控件進(jìn)行通信峦嗤,CoordinatorLayout會(huì)通過(guò)behavior來(lái)控制子控件的位置,以及統(tǒng)一分配滑動(dòng)事件.

1.首先咋們定義一個(gè)behavior 給咱們布局里面最下面 NestSrollerView用(里面有一大堆可以滑動(dòng)的東西)并且在布局文件里面NestScrollerView標(biāo)簽里面聲明一下


image.png

這里的scroll_behavior 就是你自定義behavior的全包名

  1. 然后其它邏輯都在behavior了
    2.1 構(gòu)造函數(shù)得重寫(xiě)
 public ScrollBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        scroller = new Scroller(context);
        scrollerRefresh = new Scroller(context);
        handler = new Handler();
    }

2.2 指定一個(gè)依賴屋摔,其實(shí)就是你想監(jiān)聽(tīng)那個(gè)view的位置變化你就指定誰(shuí)

 @Override
    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull NestedScrollView child,
                                   @NonNull View dependency) {
        if (dependency.getId() == R.id.function_area) {
            dependy = new WeakReference<View>(dependency);
            return true;
        }
        return super.layoutDependsOn(parent, child, dependency);
    }

2.3 處理依賴位置變化 烁设,如果你想要依據(jù)依賴位置的變化做一些邏輯操作

 @Override
    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent,
                                          @NonNull NestedScrollView child, @NonNull View dependency) {
        child.setTranslationY(dependency.getTranslationY()*2);

        float perchent = dependency.getTranslationY() / maxFunctionCollaped * 0.3f;
//        parent.findViewById(R.id.title_bar).setAlpha(1-perchent);
        if (dependency.getTranslationY() > -maxFunctionCollaped * 0.3f) {
            parent.findViewById(R.id.title_bar).setBackgroundResource(R.mipmap.top_search_back);
        } else {
            parent.findViewById(R.id.title_bar).setBackgroundResource(R.mipmap.search_bar_collapsing);
        }
        return super.onDependentViewChanged(parent, child, dependency);
    }

2.4 指定界面初始化時(shí)的布局位置,因?yàn)镃oordinateLayout相當(dāng)于幀布局 钓试,你要在這里指定一些位置關(guān)系(通常是view的上下關(guān)系装黑,幀布局里面寫(xiě)比較費(fèi)力,在代碼里面直接碼比較直接).

  @Override
    public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull NestedScrollView child,
                                 int layoutDirection) {
        View function_area = parent.findViewById(R.id.function_area);
        View tigle_bar = parent.findViewById(R.id.title_bar);
        View  bottomLayout = parent.findViewById(R.id.bottom_layout);
        child.layout(0, function_area.getBottom(), parent.getMeasuredWidth(),
                function_area.getMeasuredHeight() + parent.getMeasuredHeight()+bottomLayout.getMeasuredHeight());
        maxFunctionCollaped = (int) (function_area.getMeasuredHeight()*0.5f);
        anim_root = child.findViewById(R.id.anim_root);
        scroll_content = child.findViewById(R.id.scroll_content);
        return true;
    }

2.5 嵌套滑動(dòng)第一步 亚侠,判斷是否進(jìn)行嵌套滑動(dòng) 曹体,每次嵌套滑動(dòng)就是從這個(gè)方法開(kāi)始的


 @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                       @NonNull NestedScrollView child, @NonNull View directTargetChild, @NonNull View target,
                                       int axes, int type) {
        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

2.6 嵌套滑動(dòng)第二步 如果第一步返回true 就會(huì)到這里來(lái)了

 @Override
    public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
                                       @NonNull NestedScrollView child, @NonNull View directTargetChild, @NonNull View target,
                                       int axes, int type) {
        scroller.abortAnimation();
        scrollerRefresh.forceFinished(true);
        isNestScrolling=false;
        super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, axes, type);
    }

2.7 嵌套滑動(dòng)第三步 開(kāi)始分配滑動(dòng)距離

  @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                  @NonNull NestedScrollView child, @NonNull View target, int dx, int dy,
                                  @NonNull int[] consumed, int type) {
        View dependView = dependy.get();
        if (dy > 0 && dependView.getTranslationY() <= 0 && (anim_root.getTranslationY() == 0||anim_root.getTranslationY()==anim_root.getMeasuredHeight())) {   //刷新布局未偏移 , 向上滑動(dòng) 且 上部分折疊區(qū)未折疊或正在折疊
            dy=(int)(Math.abs(dy));
            int currentTranslation = (int) dependView.getTranslationY();
            int targetDy = currentTranslation - dy;
            int concorrect = targetDy < -maxFunctionCollaped ? -maxFunctionCollaped : targetDy;
            dependView.setTranslationY(concorrect);
            consumed[1] = currentTranslation - concorrect;
        } else if (dy < 0 && dependView.getTranslationY() >= 0) {// 向下滑動(dòng) 且上部分折疊區(qū)未折疊
            int currentTranslationY = (int) anim_root.getTranslationY();
            dy=-(int)(Math.abs(dy)*0.6f);
            float calculate = currentTranslationY - dy > anim_root.getMeasuredHeight() ? anim_root.getMeasuredHeight() : currentTranslationY - dy ;
           //calculate+=(calculate/anim_root.getMeasuredHeight())*dy; //阻尼效果
            anim_root.setTranslationY(calculate);
            scroll_content.setTranslationY(calculate);
            consumed[1] = (int) (calculate - currentTranslationY);
        } else if (dy > 0 && anim_root.getTranslationY() > 0) {  //向上滑動(dòng) 硝烂,刷新布局已經(jīng)劃出來(lái)
            int currentTranslationY = (int) anim_root.getTranslationY();
            int calculate = currentTranslationY - dy >= 0 ? currentTranslationY - dy : 0;
            anim_root.setTranslationY(calculate);
            scroll_content.setTranslationY(calculate);
            consumed[1] = dy;
        }

        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
    }

這里dy表示y方向 感應(yīng)到的滑動(dòng)量 dy>0表示向上滑動(dòng) dy<0表示向下滑動(dòng),然后你可以根據(jù)dy以及當(dāng)前控件的位置來(lái)決定用哪一個(gè)控件來(lái)消耗這個(gè)dy ,然后把你的消耗值傳給consumed[1] 铜幽,如果你把dy消耗完了滞谢,那里面嵌套的可滑動(dòng)控件NestScrollerview就不會(huì)滑動(dòng)內(nèi)部?jī)?nèi)容了,如果沒(méi)用完除抛,那么NestScrollerview會(huì)緊接著onNestedPreScroll你自己定義的滑動(dòng)繼續(xù)滑動(dòng)NestScrollerview里面的內(nèi)容

2.8 嵌套滑動(dòng)第四步:可滑動(dòng)控件NestScrollerview 滑動(dòng)完了(前提是它有得滑)緊接著就會(huì)到 onNestedScroll方法

 @Override
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                               @NonNull NestedScrollView child, @NonNull View target, int dxConsumed, int dyConsumed,
                               int dxUnconsumed, int dyUnconsumed, int type) {
        if (dyUnconsumed < 0 && dependy.get().getTranslationY() < 0) {
            View dependView = dependy.get();
            int currentTranslation = (int) dependView.getTranslationY();
            int targetDy = currentTranslation - dyUnconsumed;
            int concorrect = targetDy > 0 ? 0 : targetDy;
            dependView.setTranslationY(concorrect);
            dyConsumed = currentTranslation - concorrect;
            isNestScrolling=true;
        }
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
                dyUnconsumed, type);
    }

如果可滑動(dòng)控件NestScrollerview滑動(dòng)(它是在滑動(dòng)它里面的內(nèi)容)完了 還有未消耗完的滑動(dòng)量 你可以在這里拿到滑動(dòng)量dyUnconsumed,繼續(xù)定義緊接著NestScrollerview滑動(dòng)完的其它滑動(dòng)事件

2.9 慣性滑動(dòng)處理 ---滑動(dòng)結(jié)束的一種情況(不一定會(huì)調(diào)用)

    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
                                    @NonNull NestedScrollView child, @NonNull View target, float velocityX, float velocityY) {
        onRefreshEnd();
        Log.d("onNestedStroll","fling");
        return onDragEnd(velocityY);
    }

一般就是快速滑動(dòng)馬上抬手就會(huì)出現(xiàn)狮杨,這里會(huì)返回滑動(dòng)速度給你,參考這個(gè)數(shù)值來(lái)處理滑動(dòng)view的最終停留狀態(tài)。

2.10 滑動(dòng)結(jié)束的回調(diào)到忽,不同于上個(gè)方法這個(gè) 一定會(huì)觸發(fā)的

 @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                   @NonNull NestedScrollView child, @NonNull View target, int type) {
        Log.d("onNestedStroll","onStopNestedScroll");
        onRefreshEnd();
        onDragEnd(600);
        isNestScrolling=false;
    }

滑動(dòng)結(jié)束后 必須確定View的最終位置(到底是展開(kāi)還是縮起來(lái))橄教,擺在中間不太好看吧,這里需要借助scroll開(kāi)展平滑動(dòng)畫(huà)來(lái)將你的view從中間態(tài)移動(dòng)到你最終希望它到達(dá)的位置喘漏,達(dá)到一種彈性效果护蝶。

onNestedPreFling onStopNestedScroll兩個(gè)方法處理滑動(dòng)結(jié)束的邏輯比較統(tǒng)一也相對(duì)簡(jiǎn)單,而且邏輯基本上都是一樣的翩迈。麻煩點(diǎn)是動(dòng)畫(huà)播放時(shí)進(jìn)行嵌套移動(dòng)以及如何分配滑動(dòng)量給子view

博文里只能簡(jiǎn)述基本流程持灰,詳情請(qǐng)移步倉(cāng)庫(kù)
https://github.com/nokiafen/viewpro/tree/master/alihomepage

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市负饲,隨后出現(xiàn)的幾起案子堤魁,更是在濱河造成了極大的恐慌喂链,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妥泉,死亡現(xiàn)場(chǎng)離奇詭異椭微,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)盲链,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)蝇率,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人匈仗,你說(shuō)我怎么就攤上這事瓢剿。” “怎么了悠轩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵间狂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我火架,道長(zhǎng)鉴象,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任何鸡,我火速辦了婚禮纺弊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘骡男。我一直安慰自己淆游,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布隔盛。 她就那樣靜靜地躺著犹菱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吮炕。 梳的紋絲不亂的頭發(fā)上腊脱,一...
    開(kāi)封第一講書(shū)人閱讀 49,837評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音龙亲,去河邊找鬼陕凹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鳄炉,可吹牛的內(nèi)容都是我干的杜耙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼迎膜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泥技!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤珊豹,失蹤者是張志新(化名)和其女友劉穎簸呈,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體店茶,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜕便,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贩幻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轿腺。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丛楚,靈堂內(nèi)的尸體忽然破棺而出族壳,到底是詐尸還是另有隱情,我是刑警寧澤趣些,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布仿荆,位于F島的核電站,受9級(jí)特大地震影響坏平,放射性物質(zhì)發(fā)生泄漏拢操。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一舶替、第九天 我趴在偏房一處隱蔽的房頂上張望令境。 院中可真熱鬧,春花似錦顾瞪、人聲如沸舔庶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)栖茉。三九已至,卻和暖如春孵延,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亲配。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工尘应, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吼虎。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓犬钢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親思灰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子玷犹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容