亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Android自定義ViewGroup實現淘寶商品詳情頁

2019-10-21 21:37:12
字體:
來源:轉載
供稿:網友

最近公司在新版本上有一個需要,要在首頁添加一個滑動效果,具體就是仿照X寶的商品詳情頁,拉到頁面底部時有一個粘滯效果,如下圖X東的商品詳情頁,如果用戶繼續向上拉的話就進入商品圖文描述界面:

Android,ViewGroup,淘寶,商品詳情頁

剛開始是想拿來主義,直接從網上找個現成的demo來用, 但是網上無一例外的答案都特別統一: 幾乎全部是ScrollView中再套兩個ScrollView,或者是一個LinearLayout中套兩個ScrollView。 通過指定父view和子view的focus來切換滑動的處理界面---即通過view的requestDisallowInterceptTouchEvent方法來決定是哪一個ScrollView來處理滑動事件。

使用以上方法雖然可以解一時之渴, 但是存在幾點缺陷:

1  擴展性不強 : 如果后續產品要求不止是兩頁滑動呢,是三頁滑動呢, 難道要嵌3個ScrollView并通過N個判斷來實現嗎

2  兼容性不強 : 如果需要在某一個子頁中需要處理左右滑動事件或者雙指操作事件呢, 此方法就無法實現了

3 個人原因 : 個人喜歡自己掌握主動性,事件的處理自己來控制更靠譜一些(PS:就如同一份感情一樣,需要細心去經營)

總和以上原因, 自己實現了一個ViewGroup,實現文章開頭提到的效果, 廢話不多說  直接上源碼,以下只是部分主要源碼,并對每一個方法都做了注釋,可以參照注釋理解。   文章最后對這個ViewGroup加了一點實現的細節以及如何使用此VIewGroup, 以及demo地址

package com.mcoy.snapscrollview; import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.widget.Scroller; /** * @author jiangxinxing---mcoy in English *  * 了解此ViewGroup之前, 有兩點一定要做到心中有數 * 一個是對Scroller的使用, 另一個是對onInterceptTouchEvent和onTouchEvent要做到很熟悉 * 以下幾個網站可以做參考用 * http://blog.csdn.net/bigconvience/article/details/26697645 * http://blog.csdn.net/androiddevelop/article/details/8373782 * http://blog.csdn.net/xujainxing/article/details/8985063 */public class McoySnapPageLayout extends ViewGroup {   。。。。   public interface McoySnapPage { /** * 返回page根節點 *  * @return */ View getRootView();  /** * 是否滑動到最頂端 * 第二頁必須自己實現此方法,來判斷是否已經滑動到第二頁的頂部 * 并決定是否要繼續滑動到第一頁 */ boolean isAtTop();  /** * 是否滑動到最底部 * 第一頁必須自己實現此方法,來判斷是否已經滑動到第二頁的底部 * 并決定是否要繼續滑動到第二頁 */ boolean isAtBottom(); }  public interface PageSnapedListener {  /** * @mcoy * 當從某一頁滑動到另一頁完成時的回調函數 */ void onSnapedCompleted(int derection); }   。。。。。。  /** * 設置上下頁面 * @param pageTop * @param pageBottom */ public void setSnapPages(McoySnapPage pageTop, McoySnapPage pageBottom) { mPageTop = pageTop; mPageBottom = pageBottom; addPagesAndRefresh(); }  private void addPagesAndRefresh() { // 設置頁面id mPageTop.getRootView().setId(0); mPageBottom.getRootView().setId(1); addView(mPageTop.getRootView()); addView(mPageBottom.getRootView()); postInvalidate(); }  /** * @mcoy add * computeScroll方法會調用postInvalidate()方法, 而postInvalidate()方法中系統 * 又會調用computeScroll方法, 因此會一直在循環互相調用, 循環的終結點是在computeScrollOffset() * 當computeScrollOffset這個方法返回false時,說明已經結束滾動。 *  * 重要:真正的實現此view的滾動是調用scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); */ @Override public void computeScroll() { //先判斷mScroller滾動是否完成 if (mScroller.computeScrollOffset()) { if (mScroller.getCurrY() == (mScroller.getFinalY())) { if (mNextDataIndex > mDataIndex) {  mFlipDrection = FLIP_DIRECTION_DOWN;  makePageToNext(mNextDataIndex); } else if (mNextDataIndex < mDataIndex) {  mFlipDrection = FLIP_DIRECTION_UP;  makePageToPrev(mNextDataIndex); }else{  mFlipDrection = FLIP_DIRECTION_CUR; } if(mPageSnapedListener != null){  mPageSnapedListener.onSnapedCompleted(mFlipDrection); } } //這里調用View的scrollTo()完成實際的滾動 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //必須調用該方法,否則不一定能看到滾動效果 postInvalidate(); } }  private void makePageToNext(int dataIndex) { mDataIndex = dataIndex;  mCurrentScreen = getCurrentScreen(); }  private void makePageToPrev(int dataIndex) { mDataIndex = dataIndex;  mCurrentScreen = getCurrentScreen(); }  public int getCurrentScreen() { for (int i = 0; i < getChildCount(); i++) { if (getChildAt(i).getId() == mDataIndex) { return i; } } return mCurrentScreen; }  public View getCurrentView() { for (int i = 0; i < getChildCount(); i++) { if (getChildAt(i).getId() == mDataIndex) { return getChildAt(i); } } return null; }  /* * (non-Javadoc) *  * @see * android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent) * 重寫了父類的onInterceptTouchEvent(),主要功能是在onTouchEvent()方法之前處理 * touch事件。包括:down、up、move事件。 * 當onInterceptTouchEvent()返回true時進入onTouchEvent()。 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { return true; } final float x = ev.getX(); final float y = ev.getY();  switch (action) { case MotionEvent.ACTION_MOVE: // 記錄y與mLastMotionY差值的絕對值。   // yDiff大于gapBetweenTopAndBottom時就認為界面拖動了足夠大的距離,屏幕就可以移動了。 final int yDiff = (int)(y - mLastMotionY); boolean yMoved = Math.abs(yDiff) > gapBetweenTopAndBottom; if (yMoved) { if(MCOY_DEBUG) {  Log.e(TAG, "yDiff is " + yDiff);  Log.e(TAG, "mPageTop.isFlipToBottom() is " + mPageTop.isAtBottom());  Log.e(TAG, "mCurrentScreen is " + mCurrentScreen);  Log.e(TAG, "mPageBottom.isFlipToTop() is " + mPageBottom.isAtTop()); } if(yDiff < 0 && mPageTop.isAtBottom() && mCurrentScreen == 0   || yDiff > 0 && mPageBottom.isAtTop() && mCurrentScreen == 1){  Log.e("mcoy", "121212121212121212121212");  mTouchState = TOUCH_STATE_SCROLLING; } } break; case MotionEvent.ACTION_DOWN: // Remember location of down touch mLastMotionY = y; Log.e("mcoy", "mScroller.isFinished() is " + mScroller.isFinished()); mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST  : TOUCH_STATE_SCROLLING; break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // Release the drag mTouchState = TOUCH_STATE_REST; break; } boolean intercept = mTouchState != TOUCH_STATE_REST; Log.e("mcoy", "McoySnapPageLayout---onInterceptTouchEvent return " + intercept); return intercept; }  /* * (non-Javadoc) *  * @see android.view.View#onTouchEvent(android.view.MotionEvent) * 主要功能是處理onInterceptTouchEvent()返回值為true時傳遞過來的touch事件 */ @Override public boolean onTouchEvent(MotionEvent ev) { Log.e("mcoy", "onTouchEvent--" + System.currentTimeMillis());  if (mVelocityTracker == null) {   mVelocityTracker = VelocityTracker.obtain();  }  mVelocityTracker.addMovement(ev);   final int action = ev.getAction(); final float x = ev.getX(); final float y = ev.getY(); switch (action) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; case MotionEvent.ACTION_MOVE:  if(mTouchState != TOUCH_STATE_SCROLLING){     // 記錄y與mLastMotionY差值的絕對值。     // yDiff大于gapBetweenTopAndBottom時就認為界面拖動了足夠大的距離,屏幕就可以移動了。    final int yDiff = (int) Math.abs(y - mLastMotionY);    boolean yMoved = yDiff > gapBetweenTopAndBottom;    if (yMoved) {     mTouchState = TOUCH_STATE_SCROLLING;    }   }   // 手指拖動屏幕的處理   if ((mTouchState == TOUCH_STATE_SCROLLING)) {    // Scroll to follow the motion event    final int deltaY = (int) (mLastMotionY - y);    mLastMotionY = y;    final int scrollY = getScrollY();    if(mCurrentScreen == 0){//顯示第一頁,只能上拉時使用     if(mPageTop != null && mPageTop.isAtBottom()){     scrollBy(0, Math.max(-1 * scrollY, deltaY));     }    }else{     if(mPageBottom != null && mPageBottom.isAtTop()){     scrollBy(0, deltaY);     }    }   } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // 彈起手指后,切換屏幕的處理 if (mTouchState == TOUCH_STATE_SCROLLING) {  final VelocityTracker velocityTracker = mVelocityTracker;    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);    int velocityY = (int) velocityTracker.getYVelocity();    if (Math.abs(velocityY) > SNAP_VELOCITY) {     if( velocityY > 0 && mCurrentScreen == 1 && mPageBottom.isAtTop()){      snapToScreen(mDataIndex-1);     }else if(velocityY < 0 && mCurrentScreen == 0){      snapToScreen(mDataIndex+1);     }else{      snapToScreen(mDataIndex);     }    } else {     snapToDestination();    }    if (mVelocityTracker != null) {     mVelocityTracker.recycle();     mVelocityTracker = null;    } }else{ } mTouchState = TOUCH_STATE_REST; break;  default: break; } return true; }  private void clearOnTouchEvents(){ mTouchState = TOUCH_STATE_REST; if (mVelocityTracker != null) {    mVelocityTracker.recycle();    mVelocityTracker = null;   } }  private void snapToDestination() { // 計算應該去哪個屏 final int flipHeight = getHeight() / 8;    int whichScreen = -1;  final int topEdge = getCurrentView().getTop();   if(topEdge < getScrollY() && (getScrollY()-topEdge) >= flipHeight && mCurrentScreen == 0){   //向下滑動    whichScreen = mDataIndex + 1;  }else if(topEdge > getScrollY() && (topEdge - getScrollY()) >= flipHeight && mCurrentScreen == 1){   //向上滑動   whichScreen = mDataIndex - 1;  }else{   whichScreen = mDataIndex;  }  Log.e(TAG, "snapToDestination mDataIndex = " + mDataIndex);  Log.e(TAG, "snapToDestination whichScreen = " + whichScreen);  snapToScreen(whichScreen); }  private void snapToScreen(int dataIndex) {  if (!mScroller.isFinished())   return;    final int direction = dataIndex - mDataIndex;  mNextDataIndex = dataIndex;  boolean changingScreens = dataIndex != mDataIndex;  View focusedChild = getFocusedChild();  if (focusedChild != null && changingScreens) {   focusedChild.clearFocus();  }  //在這里判斷是否已到目標位置~  int newY = 0; switch (direction) { case 1: //需要滑動到第二頁 Log.e(TAG, "the direction is 1"); newY = getCurrentView().getBottom(); // 最終停留的位置 break; case -1: //需要滑動到第一頁 Log.e(TAG, "the direction is -1"); Log.e(TAG, "getCurrentView().getTop() is "  + getCurrentView().getTop() + " getHeight() is "  + getHeight()); newY = getCurrentView().getTop() - getHeight(); // 最終停留的位置 break; case 0: //滑動距離不夠, 因此不造成換頁,回到滑動之前的位置 Log.e(TAG, "the direction is 0"); newY = getCurrentView().getTop(); //第一頁的top是0, 第二頁的top應該是第一頁的高度 break; default: break; }  final int cy = getScrollY(); // 啟動的位置  Log.e(TAG, "the newY is " + newY + " cy is " + cy);  final int delta = newY - cy; // 滑動的距離,正值是往左滑<—,負值是往右滑—>  mScroller.startScroll(0, cy, 0, delta, Math.abs(delta));  invalidate(); }  }

McoySnapPage是定義在VIewGroup的一個接口, 比如說我們需要類似某東商品詳情那樣,有上下兩頁的效果。 那我就需要自己定義兩個類實現這個接口,并實現接口的方法。getRootView需要返回當前頁需要顯示的布局內容;isAtTop需要返回當前頁是否已經在頂端; isAtBottom需要返回當前頁是否已經在底部

onInterceptTouchEventonTouchEvent決定當前的滑動狀態, 并決定是有當前VIewGroup攔截touch事件還是由子view去消費touch事件

Demo地址

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
色多多国产成人永久免费网站| 97视频在线观看播放| 亚洲午夜精品久久久久久性色| 国产精品久久久久久久久久99| 国产精品自拍视频| 中文字幕少妇一区二区三区| 欧美日韩在线另类| 69视频在线播放| 欧美亚洲国产日韩2020| 青青草一区二区| 久久久久久国产三级电影| 国产成人极品视频| 91久久中文字幕| 精品福利樱桃av导航| 亚洲国产欧美日韩精品| 懂色av中文一区二区三区天美| 91亚洲精品视频| 在线精品视频视频中文字幕| 亚洲xxx自由成熟| 成人激情在线观看| 综合av色偷偷网| 国产精品欧美日韩| 国产精品欧美激情在线播放| 欧美日韩国产区| 日韩精品在线观看一区| 91精品国产高清自在线| 久久91超碰青草是什么| 国内精品视频一区| 久久中文字幕视频| 欧美一区二粉嫩精品国产一线天| 性色av一区二区三区红粉影视| 国产精品va在线| 91在线免费观看网站| 久久久www成人免费精品张筱雨| 亚洲欧美中文日韩在线v日本| 亚洲一区二区三区四区视频| 欧美激情免费在线| 91高清免费视频| 一个色综合导航| 欧美成人在线影院| 精品国模在线视频| 国产乱人伦真实精品视频| 欧美大成色www永久网站婷| 日韩电影中文 亚洲精品乱码| 亚洲xxxx视频| 欧美黑人又粗大| 日韩av电影国产| 日韩av在线网| 欧美视频在线观看 亚洲欧| 国产精品久久久久久亚洲调教| 午夜精品一区二区三区在线视| 亚洲无限乱码一二三四麻| 亚洲老板91色精品久久| 日韩高清不卡av| 亚洲精品永久免费| 欧美精品一区二区免费| 影音先锋欧美在线资源| 日韩视频一区在线| 红桃视频成人在线观看| 国产黑人绿帽在线第一区| 欧美激情一区二区三区久久久| 97视频免费在线看| 日韩激情在线视频| 国产精品久久久久久亚洲调教| 久久人91精品久久久久久不卡| 日韩麻豆第一页| 中文字幕日韩在线播放| 国产一区二区三区四区福利| 国产精品久久一区主播| 成人啪啪免费看| 中文字幕亚洲无线码在线一区| 亚洲综合av影视| 影音先锋欧美精品| 亚洲最新av在线| 亚洲美女视频网站| 97精品一区二区视频在线观看| 欧美疯狂xxxx大交乱88av| 国产成人激情视频| 欧美大全免费观看电视剧大泉洋| 久久久免费观看| 久久九九亚洲综合| 国产精品偷伦免费视频观看的| 久久成人这里只有精品| 国产午夜精品视频免费不卡69堂| 4438全国亚洲精品在线观看视频| 亚洲一区二区久久久| 性欧美办公室18xxxxhd| 亚洲欧美精品一区二区| 欧美高清性猛交| 久久精品成人动漫| 欧美精品在线第一页| 中文字幕亚洲欧美| 久久手机免费视频| 一区二区三区 在线观看视| 欧美裸体xxxx极品少妇软件| 亚洲tv在线观看| 欧美性猛交xxxx| 亚洲国产欧美自拍| 精品美女久久久久久免费| 国产精品视频网站| 国模吧一区二区| 91国产视频在线| 5278欧美一区二区三区| 国产午夜精品久久久| 国产在线观看不卡| 国内伊人久久久久久网站视频| 亚洲成人精品视频在线观看| 姬川优奈aav一区二区| 日韩精品免费观看| 中文字幕在线国产精品| 欧美另类老肥妇| 国产啪精品视频网站| 日韩欧美中文字幕在线观看| 国产精品久久久久福利| 国产成人高潮免费观看精品| 欧美国产第二页| 深夜成人在线观看| 亚洲国产精久久久久久| 亚洲国产精品视频在线观看| 黑人巨大精品欧美一区二区一视频| 91老司机在线| 大胆人体色综合| 国产成人精品最新| 日韩大片免费观看视频播放| 深夜福利日韩在线看| 国产精品久久久久久影视| 欧美成人午夜剧场免费观看| 亚洲精品国产免费| 97视频免费在线观看| 欧美性受xxxx白人性爽| 亚洲天堂av高清| 欧美一区二区三区……| 国产精品福利在线观看网址| 欧美在线不卡区| 国产成人高清激情视频在线观看| 91免费版网站入口| 色悠悠久久久久| 亚洲丝袜一区在线| 国产精品高清在线观看| 国产综合福利在线| 激情av一区二区| 日韩av片永久免费网站| 日韩精品免费在线视频观看| 国产精品极品美女粉嫩高清在线| 日本国产一区二区三区| 国产成人福利视频| 久热精品视频在线免费观看| 亚洲自拍小视频免费观看| 久久久久亚洲精品成人网小说| 亚洲大胆人体在线| 欧美激情欧美激情在线五月| 日韩在线观看免费网站| 日韩免费在线播放| 精品人伦一区二区三区蜜桃网站| 欧美国产日韩一区| 久久精品视频在线播放| 国产精品亚洲第一区| 青青草原成人在线视频| 日本免费一区二区三区视频观看| 国内精品一区二区三区四区| 懂色av中文一区二区三区天美| 国产亚洲美女久久| 国内精久久久久久久久久人| 成人久久一区二区三区|