scrollview嵌套上下拉控制項嵌套recyclerview

来源:http://www.cnblogs.com/LiuZhen/archive/2017/06/30/7100505.html
-Advertisement-
Play Games

相信會碰到很多類似的需求,一個列表控制項,然後控制項上方的一個頭部需要自定義,這樣就不好有時候也不能加在列表控制項的頭部了,那必須得嵌套一層scrollview了,沒毛病,那麼一般的列表控制項都是有上拉下拉的操作,而且一般也是在 github 上找尋一個收藏量高的 來做為一個全局通用的上下拉控制項,這裡問題就 ...


 

相信會碰到很多類似的需求,一個列表控制項,然後控制項上方的一個頭部需要自定義,這樣就不好有時候也不能加在列表控制項的頭部了,那必須得嵌套一層scrollview了,沒毛病,那麼一般的列表控制項都是有上拉下拉的操作,而且一般也是在 github 上找尋一個收藏量高的

來做為一個全局通用的上下拉控制項,這裡問題就來了,一般的 scrollview 嵌套 recyclerview 或者 listview 都畢竟容易解決,可是在加上一層上下拉控制項呢?上下拉控制項肯定會有它自己的觸摸處理機制,這樣你改起來也很麻煩,這種滑動觸摸的問題最是難搞,也有的是scrollview

嵌套 viewpage 嵌套 recyclerview 的,不過這種效果網上還是有些方案的,搜索material design效果也有一些現成的,不過 scrollview 嵌套 SwipeRefreshLayout 嵌套 recyclerview 或者 scrollview 嵌套 PtrClassicFrameLayout 嵌套 recyclerview 很少,我搜了好久沒有滿足我需求的,而且很多是自定義了 recyclerview 和下拉控制項才解決的,這樣的話它不是用的 github 原控制項,可以說是自己寫的,可項目里很多都是有固定的一套第三方庫,你不能改,除非你是項目負責人,一開始就這樣規定了,不然就得想辦法去解決

還有一種網上的方案是退而求其次,用 NestedScrollView,不過這也有一些問題,就是下滑的時候對於侵入式的下拉方式有效果,可是對於非侵入刷新就不好使了,而且我個人也更喜歡非侵入刷新,所以花了些空閑的時間搗鼓了一個demo,解決了一個心頭之患,下次遇到就能輕鬆的對待了,不用在去糾結用這個庫是否會有問題了

一開始做的走了個誤區,沒有思考清楚,大致思路就是首先把下拉的控制項禁止 setEnabled(false) ,然後scrollview攔截事件,判斷滑動,超出了頭部的距離就讓子控制項滑動,這是一個大致的思路,然後就開幹了,可想法是美好的,現實是殘酷的,並沒有取得成功,而且還把自己的思路攪渾了

先看失敗的效果圖

咋一看好像沒啥問題,而且簡單,這還說這麼多做什麼,其實只是效果沒有顯示出來(顯示不出來),這就尷尬了⊙﹏⊙‖∣

如果直接把上下拉控制項包住頭部其實沒有問題,但是這樣你下拉的時候在頂端顯示效果,這樣頭部和直接加入到列表控制項里有什麼區別,所以重新創建了個類,從頭再來,好好在理理思路,先從簡單的開始,SwipeRefreshLayout 是android自帶的下拉刷新庫,而且是侵入式的下拉刷新,這樣處理起來更簡單,柿子挑軟的捏了,這樣都搞不定那就猝死在電腦邊吧,結果肯定是成功了,先看效果圖

this is scrollView其實就是一個固定的頭部,測試用的,可以換成自己的,而且還能根據滑動添加動畫,基本的需求都滿足了,直接看代碼,代碼粗糙了點
  1 package com.example.demo.test2;
  2 
  3 import android.os.Handler;
  4 import android.support.v4.widget.SwipeRefreshLayout;
  5 import android.support.v7.widget.LinearLayoutManager;
  6 import android.support.v7.widget.RecyclerView;
  7 import android.view.LayoutInflater;
  8 import android.view.View;
  9 import android.view.ViewGroup;
 10 import android.view.ViewTreeObserver;
 11 import android.widget.TextView;
 12 import android.widget.Toast;
 13 
 14 import com.example.demo.BaseActivity;
 15 import com.example.demo.R;
 16 
 17 import butterknife.BindView;
 18 
 19 public class Test3Activity extends BaseActivity {
 20 
 21     @BindView(R.id.scrollView)
 22     MyNestedScroll scrollView;
 23     @BindView(R.id.tv_head)
 24     TextView tv_head;
 25     @BindView(R.id.layout)
 26     SwipeRefreshLayout layout;
 27     @BindView(R.id.recyclerView)
 28     RecyclerView recyclerView;
 29     private boolean hasMeasured;
 30 
 31     @Override
 32     protected int getLayoutId() {
 33         return R.layout.activity_test3;
 34     }
 35 
 36     @Override
 37     protected void initView() {
 38         recyclerView.setLayoutManager(new LinearLayoutManager(Test3Activity.this));
 39         recyclerView.setAdapter(new ContentAdapter());
 40         ViewTreeObserver vto = tv_head.getViewTreeObserver();
 41         vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
 42             public boolean onPreDraw() {
 43                 if (hasMeasured == false) {
 44                     int height = tv_head.getMeasuredHeight();
 45                     scrollView.setHeadHeight(height);
 46                     hasMeasured = true;
 47                 }
 48                 return true;
 49             }
 50         });
 51         scrollView.getViewTreeObserver().addOnScrollChangedListener(new  ViewTreeObserver.OnScrollChangedListener() {
 52             @Override
 53             public void onScrollChanged() {
 54                 boolean b = scrollView.getScrollY()==0;
 55                 layout.setEnabled(b);
 56             }
 57         });
 58         //設置刷新時動畫的顏色,可以設置4個
 59         layout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light,
 60                 android.R.color.holo_orange_light, android.R.color.holo_green_light);
 61         layout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
 62 
 63             @Override
 64             public void onRefresh() {
 65                 //正在刷新
 66                 // TODO Auto-generated method stub
 67                 new Handler().postDelayed(new Runnable() {
 68 
 69                     @Override
 70                     public void run() {
 71                         // TODO Auto-generated method stub
 72                         //刷新完成
 73                         layout.setRefreshing(false);
 74                     }
 75                 }, 500);
 76             }
 77         });
 78 
 79     }
 80 
 81     class ContentAdapter extends RecyclerView.Adapter {
 82         @Override
 83         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
 84             return new ContentViewHolder(LayoutInflater.from(Test3Activity.this).inflate(R.layout.item_test, parent, false));
 85         }
 86 
 87         @Override
 88         public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
 89             final ContentViewHolder viewHolder = (ContentViewHolder) holder;
 90             viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
 91                 @Override
 92                 public void onClick(View view) {
 93                     Toast.makeText(Test3Activity.this, ""+position, Toast.LENGTH_SHORT).show();
 94                 }
 95             });
 96         }
 97 
 98         @Override
 99         public int getItemCount() {
100             return 15;
101         }
102     }
103 
104     class ContentViewHolder extends RecyclerView.ViewHolder {
105 
106         public ContentViewHolder(View itemView) {
107             super(itemView);
108         }
109     }
110 }
MainActivity
 1 package com.example.demo.test2;
 2 
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.view.MotionEvent;
 6 import android.widget.ScrollView;
 7 
 8 /**
 9  * Created by LiuZhen on 2017/6/8.
10  */
11 
12 public class MyNestedScroll extends ScrollView {
13 
14     private int headHeight = 300;//預設高度,一般動態設置
15     private int downY;
16 
17     public MyNestedScroll(Context context) {
18         super(context);
19     }
20 
21     public MyNestedScroll(Context context, AttributeSet attrs) {
22         super(context, attrs);
23     }
24 
25     public MyNestedScroll(Context context, AttributeSet attrs, int defStyleAttr) {
26         super(context, attrs, defStyleAttr);
27     }
28 
29     @Override
30     public boolean onInterceptTouchEvent(MotionEvent e) {
31         int action = e.getAction();
32         int dis = getScrollY();
33         switch (action) {
34             case MotionEvent.ACTION_DOWN:
35                 downY = (int) e.getRawY();
36                 break;
37             case MotionEvent.ACTION_MOVE:
38                 int moveY = (int) e.getRawY();
39                 /**判斷是向下滑動**/
40                 if(downY-moveY>0){
41                     if (dis < headHeight && dis >= 0){
42                         return true;//滑動距離小於頭部並且沒有超出頭部攔截事件,讓本身去滑動
43                     }else{
44                         return false;//不攔截事件
45                     }
46                 }else{
47                 }
48         }
49 
50         return super.onInterceptTouchEvent(e);
51     }
52 
53     public void setHeadHeight(int headHeight) {
54         this.headHeight = headHeight;
55     }
56 
57 }
MyNestedScroll

這個很簡單,主要的邏輯也就一個方法里

然後在github上又找了個下拉庫,收藏畢竟多

 

 結果不行了,上滑一小段距離,然後在下拉,結果拉不動了,直接就顯示了上拉效果,這顯然不正常,問題一個一個來解決,肯定是有其它問題的,不過先把眼前的解決了才能下一步,要解決這個問題就得禁止 PtrClassicFrameLayout 的下拉了,問題的根源很有可能是我們用的上下拉控制項的滑動刷新效果起衝突了,最好的辦法肯定是直接禁止了,所以就給scrollview來個監聽 onScrollChanged ,獲取scrollview的scrolly,如果scrolly是0,就不禁止,因為在頂部了,不是0就禁止下拉操作,結果好了,可惜新的問題來了,上拉沒反應了,因為被禁止了,所以需要判斷滑動到了底部讓下拉效果出現

 

 於是有了這麼兩個判斷,然後肯定還是有問題的,上拉效果沒事了,下拉效果出事了,滑動到頂端的時候下拉效果直接出現了,這樣頭部都顯示不出來了,而此時事件處於子列表控制項,所以scrollview不好去監聽了,只得在 recyclerView 上面下手了,需要監聽滑動到頂部,然後禁止上下拉效果,交給scrollview滑動,讓頭部顯示出來

可是這樣一來發現還是那樣,而調試發現的確觸發了事件,可是為什麼上下拉還是沒有禁止呢,這樣滑動不出頭部,然後調試發現被下麵的 scrollview 的 onScrollChanged 事件里覆蓋了,又設置回去了,所以還得控制一下這裡,讓scrollview的監聽不要覆蓋上面的設置,這裡只是下拉的時候才會用到,所以就有了dy這個全局參數,這樣一來就ok了

  1 package com.example.demo.test2;
  2 
  3 import android.os.Handler;
  4 import android.support.v4.widget.NestedScrollView;
  5 import android.support.v4.widget.SwipeRefreshLayout;
  6 import android.support.v7.widget.LinearLayoutManager;
  7 import android.support.v7.widget.RecyclerView;
  8 import android.util.Log;
  9 import android.view.LayoutInflater;
 10 import android.view.MotionEvent;
 11 import android.view.View;
 12 import android.view.ViewGroup;
 13 import android.view.ViewTreeObserver;
 14 import android.widget.ScrollView;
 15 import android.widget.TextView;
 16 import android.widget.Toast;
 17 
 18 import com.example.demo.BaseActivity;
 19 import com.example.demo.R;
 20 import com.example.demo.test2.MyRecyclerView;
 21 
 22 import butterknife.BindView;
 23 import in.srain.cube.views.ptr.PtrClassicFrameLayout;
 24 import in.srain.cube.views.ptr.PtrDefaultHandler2;
 25 import in.srain.cube.views.ptr.PtrFrameLayout;
 26 
 27 public class Test2Activity extends BaseActivity {
 28 
 29     @BindView(R.id.scrollView)
 30     MyNestedScroll scrollView;
 31     @BindView(R.id.tv_head)
 32     TextView tv_head;
 33     @BindView(R.id.layout)
 34     PtrClassicFrameLayout layout;
 35     @BindView(R.id.recyclerView)
 36     RecyclerView recyclerView;
 37     private boolean hasMeasured;
 38     int height,dy;
 39 
 40     @Override
 41     protected int getLayoutId() {
 42         return R.layout.activity_test2;
 43     }
 44 
 45     @Override
 46     protected void initView() {
 47         final LinearLayoutManager manager = new LinearLayoutManager(Test2Activity.this);
 48         recyclerView.setLayoutManager(manager);
 49         recyclerView.setAdapter(new ContentAdapter());
 50         ViewTreeObserver vto = tv_head.getViewTreeObserver();
 51         vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
 52             public boolean onPreDraw() {
 53                 if (hasMeasured == false) {
 54                     height = tv_head.getMeasuredHeight();
 55                     scrollView.setHeadHeight(height);//設置頭部的高度
 56                     hasMeasured = true;
 57                 }
 58                 return true;
 59             }
 60         });
 61 
 62         recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
 63             @Override
 64             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
 65 
 66 //                int visibleItemCount = manager.getChildCount();
 67 //                int totalItemCount = manager.getItemCount();
 68                 int pastVisiblesItems = manager.findFirstVisibleItemPosition();
 69                 Test2Activity.this.dy = dy;
 70                 //小於0表示下拉滑動,下拉到頂端則禁止子控制項刷新,讓scrollview滑動,頭部完全顯示後才能下拉,這裡只做下滑到頂部時禁止立刻刷新,讓scrollview滑動
 71                 if ( pastVisiblesItems == 0 && dy < 0) {
 72                     Log.e("onScrolled","dy "+dy);
 73                     layout.setEnabled(false);
 74                 }
 75             }
 76         });
 77         scrollView.getViewTreeObserver().addOnScrollChangedListener(new  ViewTreeObserver.OnScrollChangedListener() {
 78             @Override
 79             public void onScrollChanged() {
 80                 //滑動的距離超出頭部,讓列表控制項自己滑動,dy大於0是上滑操作,這裡只做滑動超出頭部後交給子列表滑動
 81                 boolean b = scrollView.getScrollY()==0;//上拉一段距離後下拉,結果直接下拉效果出現了,滑動失效
 82                 //解決上拉滑動到底部後讓上拉效果有用
 83                 if (scrollView != null && scrollView.getMeasuredHeight() <= scrollView.getScrollY()+height && dy >= 0) {
 84                     b = true;
 85 //                    Log.e("Changed","true ");
 86                 }
 87                 layout.setEnabled(b);
 88                 Log.e("Changed",""+b+" - scrollView.getScrollY "+scrollView.getScrollY());
 89             }
 90         });
 91         layout.setLastUpdateTimeRelateObject(this);
 92         layout.setPtrHandler(new PtrDefaultHandler2() {
 93 
 94             @Override
 95             public void onLoadMoreBegin(PtrFrameLayout frame) {
 96                 updateData();
 97             }
 98 
 99             @Override
100             public void onRefreshBegin(PtrFrameLayout frame) {
101                 updateData();
102             }
103 
104             @Override
105             public boolean checkCanDoLoadMore(PtrFrameLayout frame, View content, View footer) {
106                 return super.checkCanDoLoadMore(frame, recyclerView, footer);
107             }
108 
109             @Override
110             public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
111                 return super.checkCanDoRefresh(frame, recyclerView, header);
112             }
113         });
114 
115         layout.postDelayed(new Runnable() {
116             @Override
117             public void run() {
118                 // mPtrFrame.autoRefresh();
119             }
120         }, 100);
121 
122 
123     }
124 
125     protected void updateData() {
126 
127         layout.postDelayed(new Runnable() {
128             @Override
129             public void run() {
130                 layout.refreshComplete();
131             }
132         }, 1000);
133     }
134 
135     class ContentAdapter extends RecyclerView.Adapter {
136         @Override
137         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
138             return new ContentViewHolder(LayoutInflater.from(Test2Activity.this).inflate(R.layout.item_test, parent, false));
139         }
140 
141         @Override
142         public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
143             final ContentViewHolder viewHolder = (ContentViewHolder) holder;
144             viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
145                 @Override
146                 public void onClick(View view) {
147                     Toast.makeText(Test2Activity.this, ""+position, Toast.LENGTH_SHORT).show();
148                 }
149             });
150         }
151 
152         @Override
153         public int getItemCount() {
154             return 15;
155         }
156     }
157 
158     class ContentViewHolder extends RecyclerView.ViewHolder {
159 
160         public ContentViewHolder(View itemView) {
161             super(itemView);
162         }
163     }
164 }
MainActivity

侵入式的下拉刷新的確麻煩很多,但是好在自己也都解決了,而有了這兩種方案不敢說每個第三方庫都能通用,但是大部分都可以通用,只要懂得了其中的原理,自己稍加修改也行,畢竟第三方庫太多了,每個庫都有它自己的自定義,所以還是要根據獨特的情況去修改

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 自定義動畫 一.單詞部分: ①animate動畫②remove移除③validity有效性 ④required匹配⑤pattern模式 二.預習部分 1.簡述JavaScript事件和jquery事件方法不同 前者需要在 前面加on後者不要 2.調用執行jquery中的hasclass()方法能實現 ...
  • 寫一個簡單的計數js代碼 首先先寫一個div,用於存放計數器,並且通過改變樣式可以改變計數器的樣子 接下來寫js代碼 效果如圖 如果想增加其他效果可以自己設置 比如如下代碼: 將switch分支結構插入計時器中,按照其中的樣式變換 好啦,今天的這個小功能就這麼簡單的實現了,如果有什麼需要幫助的請聯繫 ...
  • 1. 參考地址 https://github.com/jpush/jpush-phonegap-plugin 2. http://www.cnblogs.com/Leo_wl/p/4319445.html ...
  • 項目Gradle Errer:Network is unreachable: connect 同時還有as的 報錯 Internal HTTP server disabled: Cannot start internal HTTP server. Git integration, JavaScrip ...
  • 1-給項目添加git git init 2-查詢當前狀態,(紅色顯示的為在工作區,綠色為暫緩區) git status 3-提交到暫緩區 git add . 4-提交到本地倉庫('xxxx'裡面為註釋) git commit -m 'xxxx' 5-查看關聯的遠程代碼倉庫(如沒有 則為空) git ...
  • 為了代碼重用,我們首先封裝一個類。這個類是HttpUtil HttpUtil.java 接著老規矩,寫一個實體類,裡面包含get/set方法,為了方便這裡命名為App.java App.java 最後寫MainActivity.java 同時為了代碼的優化,和為了UI線程操作不出毛病,可以這樣寫。 ...
  • 最近被同行的一個朋友問到一個問題“UIScrollerview上添加子控制項,給子控制項約束好佈局之後,還需要給scrollerview重新設置contentsize嗎?”於是想到了我自己曾經著手的一個項目,有一個界面就用到了scrollerView,裡面添加了子控制項,我記得當時scrollerView ...
  • 1.引言 & 160;& 160;& 160;& 160;本篇將介紹音頻播放頁面的設計情況,也希望能將自己的心得與大家分享。 2.設計情況 2.1 總體設計 & 160;& 160;& 160;& 160;XAML頁面如下: 在這裡我將這個頁面分成三行,第二行的高度為40,第三行的高度為Auto,其 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...