Android自定義LinearLayout實現(xiàn)淘寶詳情頁
1.簡單說明
淘寶詳情頁就不用我一一介紹了,昨天逛淘寶看到這個效果時,讓我想起了去年剛學習Android只會使用現(xiàn)成的時候,當時在網(wǎng)上找了一個這種效果的使用了,并不懂怎么實現(xiàn)的。現(xiàn)在就看到一種效果就想自己實現(xiàn)一下,我想這就是剛接觸某個知識時的好奇心吧
說走咱就走啊,本文只是介紹一種實現(xiàn)思路,網(wǎng)上也已經(jīng)有了很多種實現(xiàn)方式,有問題請指正
效果圖(我有很用心的找美女圖的)
2.實現(xiàn)思路
繼承LinearLayout,設置方向為垂直
控件中有兩個ScrollView,至于為什么要使用ScrollView,主要是因為內(nèi)容超過一頁時省去自己處理滑動
關鍵是事件分發(fā)處理。監(jiān)聽兩個ScrollView的滑動事件,當?shù)谝豁摶瑒拥降撞繒r,再向上拖動時,攔截事件,判斷距離,超過設定值時,滑動到第二頁,否則回彈;同理,當?shù)诙摶瑒拥巾敳繒r,再向下拖動時,攔截事件,判斷距離,超過設定值時,滑動到第一頁,否則回彈(還有很多細節(jié)需要結(jié)合代碼講解)
關于回彈和滑動換頁使用的是Scroller,對于Scroller的使用,本文不做過多解釋
3.實現(xiàn)
3.1重寫ScrollView
根據(jù)實現(xiàn)思路,我們需要監(jiān)聽ScrollView是否滑動到頂部和底部,但是ScrollView的setOnScrollChangeListener()方法在api23才添加。主要是重寫ScrollViewonScrollChanged(int l, int t, int oldl, int oldt)方法。
l:當前水平方向滾動值,和getScrollX()相等
t:當前豎直方向滾動值,和getScrollY()相等
oldl:上一次水平滾動值
oldt:上一次豎直滾動值
監(jiān)聽接口:
public interface OnScrollEndListener { void scrollToBottom(View view); void scrollToTop(View view); void scrollToMiddle(View view); }
onScrollChanged方法
@Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if(t == 0){ if (mOnScrollBottomListener != null) { mOnScrollBottomListener.scrollToTop(this); } } else if(t + getMeasuredHeight() >= getChildAt(0).getMeasuredHeight()){ if (mOnScrollBottomListener != null) { mOnScrollBottomListener.scrollToBottom(this); } } else { if (mOnScrollBottomListener != null) { mOnScrollBottomListener.scrollToMiddle(this); } } }
3.2重寫onMeasure方法、page的獲取與設置
顯示調(diào)用第二個自孩子的測量方法,不然尺寸有可能為0
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /** * 顯示調(diào)用第二個自孩子的測量方法,不然尺寸有可能為0 */ View child2 = getChildAt(1); if (child2 != null) { child2.measure(widthMeasureSpec, heightMeasureSpec); } }
在onFinishInflate中初始化兩個頁面
@Override protected void onFinishInflate() { super.onFinishInflate(); if(getChildCount() == 2){ View child1 = getChildAt(0); if (child1 instanceof ScrollEndScrollView){ scrollView1 = (ScrollEndScrollView) child1; } View child2 = getChildAt(1); if(child2 instanceof ScrollEndScrollView){ scrollView2 = (ScrollEndScrollView) child2; } } initEvent(); }
為兩個頁面設置滑動監(jiān)聽
private ScrollEndScrollView.OnScrollEndListener scrollEndListener = new ScrollEndScrollView.OnScrollEndListener() { @Override public void scrollToBottom(View view) { if(view == scrollView1){ isToBotttom = true; } } @Override public void scrollToTop(View view) { if(view == scrollView2){ isToTop = true; } } @Override public void scrollToMiddle(View view) { if(view == scrollView1){ isToBotttom = false; } if(view == scrollView2){ isToTop = false; } } };
3.3Scroller使用的幾步
Scroller的英文解釋是:
This class encapsulates scrolling. You can use scrollers (Scroller or OverScroller) to collect the data you need to produce a scrolling animation—for example, in response to a fling gesture. Scrollers track scroll offsets for you over time, but they don't automatically apply those positions to your view. It's your responsibility to get and apply new coordinates at a rate that will make the scrolling animation look smooth.
此類封裝滾動。您可以使用滾動條(滾輪或OverScroller)收集你需要制作一個滾動的動畫,例如,響應一扔手勢的數(shù)據(jù)。滾動條為您跟蹤滾動偏移量隨著時間的推移,但他們不會自動將新的位置設置到View中。你的任務是獲取并使用一個合適的速度,使?jié)L動動畫看起來更平滑。
簡而言之,有關滑動的你都可以使用這個實現(xiàn)。
需要重寫的方法
@Override public void computeScroll() { super.computeScroll(); //先判斷mScroller滾動是否完成 if (mScroller.computeScrollOffset()) { //這里調(diào)用View的scrollTo()完成實際的滾動 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //必須調(diào)用該方法,否則不一定能看到滾動效果 postInvalidate(); } }
輔助方法
//調(diào)用此方法設置滾動的相對偏移 public void smoothScrollBy(int dx, int dy) { //設置mScroller的滾動偏移量 mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, Math.max(300, Math.abs(dy))); invalidate();//這里必須調(diào)用invalidate()才能保證computeScroll()會被調(diào)用,否則不一定會刷新界面,看不到滾動效果 } //調(diào)用此方法滾動到目標位置 public void smoothScrollTo(int fx, int fy) { int dx = fx - mScroller.getFinalX(); int dy = fy - mScroller.getFinalY(); smoothScrollBy(dx, dy); }
3.4事件分發(fā)
最關鍵的部分,邏輯稍復雜,細節(jié)處理較多。這里重寫dispatchTouchEvent。
顯示第一頁時
未滑動到底部,事件由scrollView1自己處理
滑動到底部時,如果繼續(xù)向上拖動,攔截事件,父控件處理滑動;繼續(xù)向下拖動時,如果父控件(即該控件)當前滾動最后位置(mScroller.getFinalY())不為0, 如果父控件繼續(xù)滾動不會出現(xiàn)負值時(出現(xiàn)負值時會導致頭部空白,因為這時是父控件控制,scrollView1不可滑動),不攔截事件,父控件處理滑動,否則,強制滑動到0位置,并把事件下發(fā)給子控件
顯示第二頁時
未滑動到最頂部時,事件由scrollView2自己處理
滑動到頂部時,如果繼續(xù)向下拖動,攔截事件,父控件處理滑動;繼續(xù)向上拖動時,如果父控件當前滾動位置小于第一頁高度,攔截事件,父控件處理滑動,否則,滑動到第二頁起始位置,并把事件下發(fā)給子控件
ACTION_MOVE中進行事件分發(fā),ACTION_UP中進行切換頁面、回彈
關于使用scroller滑動,實現(xiàn)彈性效果,簡單實現(xiàn)請看這里簡單的彈性實現(xiàn)代碼
@Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); int yPosition = (int) ev.getY(); switch (action) { case MotionEvent.ACTION_DOWN: mScroller.abortAnimation(); mLastY = yPosition; mMoveY = 0; break; case MotionEvent.ACTION_MOVE: mMoveY = (mLastY - yPosition); mLastY = yPosition; if(isToBotttom){ if(mMoveY > 0){ //向上 smoothScrollBy(0, mMoveY); return true; } else { //向下 if(mScroller.getFinalY() != 0){ //這是出于第一頁和第二頁顯示連接處 if(getScrollY() + mMoveY > 0){ smoothScrollBy(0, mMoveY); return true; } else{ smoothScrollTo(0, 0); return super.dispatchTouchEvent(ev); } } } } else if(isToTop){ if(mMoveY < 0){ //向下 smoothScrollBy(0, mMoveY); return true; } else { //向上 if(mScroller.getFinalY() < scrollView1.getHeight()){ //這是出于第一頁和第二頁顯示連接處 smoothScrollBy(0, mMoveY); return true; } else { smoothScrollTo(0, scrollView1.getHeight()); return super.dispatchTouchEvent(ev); } } } //處理快速滑動時兩頁覆蓋問題 if(pageIndex == 0){ smoothScrollTo(0, 0); } else if(pageIndex == 1){ smoothScrollTo(0, scrollView1.getHeight()); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if(isToBotttom){ if(Math.abs(getScrollY()) > TO_NEXT_PAGE_HEIGHT){ //移動到第二頁 pageIndex = 1; smoothScrollTo(0, scrollView1.getHeight()); isToBotttom = false; isToTop = true; } else { //回彈 smoothScrollBy(0, -mScroller.getFinalY()); } } else if(isToTop){ if(scrollView1.getHeight() - getScrollY() > TO_NEXT_PAGE_HEIGHT){ //移動到第一頁 pageIndex = 0; smoothScrollTo(0, 0); isToBotttom = true; isToTop = false; } else { //回彈 smoothScrollTo(0, scrollView1.getHeight()); } } break; default: break; } return super.dispatchTouchEvent(ev); }
4.總結(jié)
實現(xiàn)該控件,需要掌握的知識點主要是自定義控件的基本步驟、Scroller的基本使用和事件分發(fā),當然這里最關鍵的處理還是事件分發(fā)。開頭也說了,雖然這個有很多人實現(xiàn)過了,但還是想用自己的方式實現(xiàn)一遍。大笑三聲,哈哈哈,又實現(xiàn)一個自定義控件…博主還在自定義控件學習階段,請謹慎使用該控件到項目中。
5.下載
https://github.com/LineChen/TwoPageLayout
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
從零開始使用gradle配置即可執(zhí)行的Hook庫詳解
這篇文章主要為大家介紹了從零開始使用gradle配置即可執(zhí)行的Hook庫詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09Android 開發(fā)中根據(jù)搜索內(nèi)容實現(xiàn)TextView中的文字部分加粗
最近遇到一個需求,需要做一個搜索功能。搜索的內(nèi)容需要加粗顯示。實現(xiàn)方法很簡單,下面通過本文給大家分享Android 開發(fā)中根據(jù)搜索內(nèi)容實現(xiàn)TextView中的文字部分加粗樣式,非常不錯,需要的朋友參考下2017-03-03Android WebView實現(xiàn)網(wǎng)頁滾動截圖
這篇文章主要為大家詳細介紹了Android WebView實現(xiàn)網(wǎng)頁滾動截圖,對整個網(wǎng)頁進行截屏,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05Android App中使用AudioManager類來編寫音頻播放器
這篇文章主要介紹了Android App中使用AudioManager類來編寫音樂播放器的方法,文中舉了一個簡單的例子實現(xiàn)了基礎的播放暫停和靜音等功能,需要的朋友可以參考下2016-04-04Android 自定義組件衛(wèi)星菜單的實現(xiàn)
這篇文章主要介紹了Android 自定義組件衛(wèi)星菜單的實現(xiàn)的相關資料,需要的朋友可以參考下2017-07-07Android中WebChromeClient和WebViewClient的區(qū)別淺析
這篇文章主要介紹了Android中WebChromeClient和WebViewClient的區(qū)別淺析,需要的朋友可以參考下2015-04-04