多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > 綜合技術 > 自定義SwipeLayout實現側滑菜單

自定義SwipeLayout實現側滑菜單

來源:程序員人生   發布時間:2016-07-04 09:04:50 閱讀次數:2530次

請尊重個人勞動成果,轉載注明出處,謝謝!
http://blog.csdn.net/amazing7/article/details/51768942

先看 SwipeLayout的效果圖

這里寫圖片描述

圖太多了,我這只上傳1張,想看 listview和GridView效果的,和想看源碼的 —> GitHub

怎樣實現后面說,先說會空話。

  最近整理之前的項目,那時有1個這樣的需求,要在ExpandableListView的ChildView上實現 編輯和刪除的側滑菜單。我當時并沒有用他人的框架,實現出來大概是這個模樣。

這里寫圖片描述

滑動事件完好,沒有沖突,子view可以取得點擊事件。

實現起來非常簡單,大概分為3步:

①、這個Item布局采取HorizontalScrollView來作為最外層布局(對,就是它,它可以橫向轉動),注意 HorizontalScrollView繼承的是FrameLayout,意味著只能有1個子布局。在這個子布局里,擺放兩個子子布局,left用于顯示內容,right顯示菜單。

②、初始化中獲得屏幕寬度

DisplayMetrics dm = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm); mScreentWidth = dm.widthPixels;

在Adapter的getChildView方法中設置leftView的寬度為屏幕寬度

// 設置leftView的大小為屏幕寬度,這樣右側的rightView就正好被擠出屏幕外 holder.leftview= convertView.findViewById(R.id.left); LayoutParams lp = holder.leftview.getLayoutParams(); lp.width = mScreentWidth;

③、getChildView方法中給convertView設置Touch監聽事件

convertView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (view != null) { //有view被滑開了,點擊其他childview時,用于還原 ViewHolder viewHolder = (ViewHolder) view.getTag(); viewHolder.hsv.smoothScrollTo(0, 0); } break; case MotionEvent.ACTION_UP: ViewHolder viewHolder = (ViewHolder) v.getTag(); view = v; // 取得HorizontalScrollView滑動的水平方向值. int scrollX = viewHolder.hsv.getScrollX(); int rightW = viewHolder.rightView.getWidth(); if (scrollX < rightW / 4) { //滑動距離小于右側布局的1/4收縮 viewHolder.hsv.smoothScrollTo(0, 0); }else { //展開 viewHolder.hSView.smoothScrollTo(rightW, 0); } break; } return true; } }); // 刪除1條后更新狀態 if (holder.hsv.getScrollX() != 0) { holder.hsv.scrollTo(0, 0); }

  簡單說1下,HorizontalScrollView在dispatchTouchEvent的時候,如果發現時橫向滑動就把事件交給onTouchEvent處理,而這個onTouchEvent方法是來自view的(viewGroup也是調用view的該方法),在view的dispatchTouchEvent中,是順序調用OnTouchListener和onTouchEvent的。我們這setOnTouchListener中自己處理了滑動事件,并且返回true,就消耗掉了事件,不會再調用onTouchEvent。

  開始說主題,假定都對 view和viewGroup的事件分發機制 、自定義viewGroup 的流程 和 Scroller 都有了初步的了解。如果沒有,我們就假定有!
  
  這里寫圖片描述
  
  算了,還沒有了解可以看看 View和ViewGroup事件分發機制源碼分析 和 自定義FlowLayout實現標簽快捷輸入框 、Android Scroller大揭秘這3篇文章。
  

1.自定義SwipeLayout

   我是繼承LinearLayout實現的,為何不繼承HorizontalScrollView或是ViewGroup?
  HorizontalScrollView其實就是1個實現轉動功能的FrameLayout,view的onMeasure和onLayout是層層實現的,我不能在HorizontalScrollView的onLayout方法中對其子view的子view直接設置為屏幕寬度。
  
  繼承ViewGroup就要自己丈量和擺放子view,開甚么玩笑,我這么懶得人。

  為了節省大家寶貴的時間,下面只說重點。

1.1 事件沖突解決

  為何有事件沖突呢? 我們知道listview是上下滑動的,而我們的這個側滑布局要左右滑動。當我們屏幕上橫向滑動時,只要略微斜了1點,那末listview就認為要上下滑動,它就把滑動事件攔截了自己交給自己的onTouchEvent處理。根本都分發不到SwipeLayout的局部中。產生效果:側滑出來1點劃不動了,卡住了…

  但是google早已看穿了1切,他們給我們提供了這個方法。

requestDisallowInterceptTouchEvent(true);

  看過源碼的火伴肯定知道,這其實就是設置1個標志位。當傳入true時,駁回父view的中斷要求。( 就是父view不能中斷事件必須分發到子view。)

  那末事件就由我們處理了,由于listview需要上下滑動事件,而SwipeLayout需要左右滑動事件,恰好各取所需,復寫dispatchTouchEvent(MotionEvent ev)方法來實現各取所需。

public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: /** * 不允許父view對觸摸事件的攔截 */ disallowParentsInterceptTouchEvent(getParent()); startX = ev.getX(); startY = ev.getY(); isHorizontalMove =false; break; case MotionEvent.ACTION_MOVE: if(!isHorizontalMove){ curX = ev.getX(); curY = ev.getY(); float dx = curX - startX; float dy = curY - startY; /** * 認為產生了滑動 */ if(dx*dx+dy*dy > mTouchSlop*mTouchSlop){ /** * 垂直滑動 */ if (Math.abs(dy) > Math.abs(dx)){ /** * 允許父view對觸摸事件攔截,讓其他view去處理事件 */ allowParentsInterceptTouchEvent(getParent()); /** * 垂直轉動復原所有item */ shrinkAllView(); }else{ /** * 水平滑動,攔截來自己處理 */ isHorizontalMove = true; /** * 為了在onTouchEvent的Move事件中第1次摹擬滑動距離不要太大, * 記錄上1次產生move的位置 */ lastX = curX; } } } break; default: break; } return super.dispatchTouchEvent(ev); }

  在ACTION_DOWN中 駁回父view攔截,那末我們就能夠開心的在ACTION_MOVE對事件進行處理(先判斷是不是產生了滑動,再判斷是 上下 還是 左右,是上下就取消對父類的駁回,讓listview去處理事件,同時讓所有劃開的SwipeLayout關閉。是左右滑動就 中斷繼續往下層分發,攔截到自己的onTouchEvent做事件處理)。

中斷分發:

@Override public boolean onInterceptTouchEvent(MotionEvent ev) { if(isHorizontalMove){ /** * 產生水平滑動,把事件中斷到本層onTouchEvent中處理 */ return true; } return super.onInterceptTouchEvent(ev); }

  我這側滑菜單里是兩個imageView默許是不可clickable的,所以就算不攔截最后也會履行我的onTouchEvent處理。但是如果是Button,ImageButton等默許是可點擊的,那末事件就傳不上來了。

由于我們不知道是哪一個父view對事件進行了攔截,所以要循環遞歸設置標志位(為了更好的兼容性,反正我這是listview攔截了)。

/** * 由于不知道是父view那1層會攔截觸摸事件,所以遞歸向上設置標志位 * 直到頂層view,就直接返回 */ private void disallowParentsInterceptTouchEvent(ViewParent parent) { if (null == parent) { return; } parent.requestDisallowInterceptTouchEvent(true); disallowParentsInterceptTouchEvent(parent.getParent()); } private void allowParentsInterceptTouchEvent(ViewParent parent) { if (null == parent) { return; } parent.requestDisallowInterceptTouchEvent(false); allowParentsInterceptTouchEvent(parent.getParent()); }

1.2 onTouchEvent處理

  當標志位表明是橫向滑動時,我們需要在ACTION_MOVE里面摹擬轉動。觸發1次ACTION_MOVE就讓內容轉動1點點,實現效果就是內容跟隨手指移動。

public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: if(isHorizontalMove){ curX = ev.getX(); float dX = curX-lastX; /** * 不斷更新lastX的位置,用于摹擬滑動 */ lastX = curX; /** * 滑動的距離與實際相反,由于轉動的時候移動的是內容,不是view */ int disX = getScrollX() + (int)(-dX); /** * 手指向右移動 */ if(disX<0){ /** * 如果菜單收縮,避免越界(越界后ACTION_UP又會轉動回來,但還是不越界的好) * 如果菜單展開,,我們希望迅速關閉菜單,不需要摹擬轉動 */ scrollTo(0, 0); } /** * 手指向左移動,如果累加的移動距離已大于menu的寬度,就讓menu顯示出來。 * 如果移動距離還不到,就摹擬轉動 */ else if(disX>rightViewWidth){ scrollTo(rightViewWidth,0); } else{ scrollTo(disX, 0); } } break; case MotionEvent.ACTION_UP: float endX = ev.getX(); float dis =endX -startX; /** * 手指向左滑動,摹擬展開 */ if(dis<0){ SimulateScroll(EXPAND); } /** * 手指向右滑動,摹擬關閉 */ else{ SimulateScroll(SHRINK); } default: break; } return true; }

  先說1下這個lastX,這個坐標是onInterceptTouchEvent中判斷為橫向移動后的最近坐標。(事件不能被消費掉,但是會隨著時間消失,我們可以在最內層到最外層的所有view的onTouchEvent中對1個事件進行處理,全部返回false。但是這里是 在onInterceptTouchEvent中已觸發了11個(大概)ACTION_MOVE事件,然后onTouchEvent才進行處理。簡單的說就是我滑動的前1段距離拿去做判斷了,判斷好了才跟這手指移動。)

  那這么辦呢?要末  

float dX = curX-lastX;

滑動順暢,不足的距離由最后scroller摹擬轉動補回。

float dX = curX-startX;

剛產生移動那1下移動約10個ACTION_MOVE事件的距離,效果不好。

1.2.1 onTouchEvent的ACTION_MOVE中摹擬轉動

給不了解的惡補1下概念:

這里寫圖片描述

getX() 和getY()是view的內部坐標,大小補回超太長寬。

這里寫圖片描述

getScrollX()

獲得的是view左上角到內容左上角的距離。至于正負表示方向。
Positive numbers will scroll the content to the left.  

 屏幕剛顯示,未產生移動之前getScrollX()等于0,每次scrollTo后會刷新getScrollX()的值。

 在1次ACTION_MOVE事件中,手指移動了dx距離,那末讓內容也1起移動dx,就實現了跟隨手指移動的效果。

相當于 getScrollX()+=dx。

1.3 用scroller摹擬繼續滑動

  當我們手指拿起來的時候,要判斷SwipeLayout的菜單欄是應當收縮還是應當展開。當肯定了狀態后就要摹擬手指觸摸滑動到指定位置。

/** * move事件里摹擬滑動完成后,判斷展開狀態 * 再摹擬轉動到目標位置 */ public void SimulateScroll(int type){ int dx =0; switch (type){ case EXPAND: //手指向左滑動getScrollX為正 dx = rightViewWidth-getScrollX(); break; case SHRINK: //手指向右滑動getScrollX為負 dx = 0-getScrollX(); break; default: break; } scroller.startScroll(getScrollX(),0,dx,0,Math.abs(dx)/2); invalidate(); } @Override public void computeScroll() { /** * Call this when you want to know the new location. If it returns true, * the animation is not yet finished. * * 返回true代表正在摹擬數據,false 已停止摹擬數據 */ if (scroller.computeScrollOffset()) { /** * 更新X軸的偏移量 */ scrollTo(scroller.getCurrX(), 0); /** * 遞歸調用computeScroll()方法,直到摹擬轉動完成 */ invalidate(); } }

 這里調用invalidate()重繪UI,會再次調用computeScroll(),遞歸 直到 摹擬轉動完成。

2. 上下滑動和刪除時狀態改變

  listview滑動時所有SwipeLayout復原。
  我們知道listview刪除1個item,不1定會回收view,有可能只是重新裝載了數據。那末顯示的時候要SwipeLayout復原。

/** * 用于上下滑動和刪除item時的,狀態改變 */ static List<SwipeLayout> swipelayouts = new ArrayList<>(); public static void addSwipeView(SwipeLayout v){ if(null==v){ return; } swipelayouts.add(v); } public static void removeSwipeView(SwipeLayout v){ if(null==v){ return; } v.SimulateScroll(SwipeLayout.SHRINK); } private void shrinkAllView(){ for(SwipeLayout s :swipelayouts){ if(null==s){ swipelayouts.remove(s); continue; }else { s.SimulateScroll(SwipeLayout.SHRINK); } } }

內部定義了3個方法,當刪除item時調用removeSwipeView方法使該view復原。在adapte的getview方法添加新item時調用addSwipeView,把當前顯示的所有SwipeLayout都裝在這個list里面。在上面dispatchTouchEvent中判斷為上下滑動事件的時候調用shrinkAllView復原所有。

基本上就完了,有木有很簡單!哈哈

3. 其他

  我們這里是解決了上下滑動和左右滑動的沖突,那末listview 中item為scrollview時,兩個都要豎著滑動。
  我們為何想要在listview 中使用scrollview,由于我們的item中需要顯示的太多。我們知道當scrollview里的內容高度 小于它父view給他的高度的時候,它是完全展開的,不需要也不能滑動。那末使listview給item足夠大的高度,讓scrollview沒必要以轉動的方式來展現,就能夠解決這個滑動沖突,反正轉動事件也會被listview 攔截。
  新建1個新的NoScrollListView繼承與listview,并復寫onMeasure方法。
  

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //這好比你有1個炒雞有錢的爹,你想要多少都能滿足你,對! 是滿足你... int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 7, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); }

那末我有1個問題,viewpage+fragment+listview +SwipeLayout,我們知道viewpage要左右滑動,當產生滑動事件的時候,我是該移動SwipeLayout呢?還是讓viewpage子去翻頁呢?

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 亚洲第一毛片 | 亚洲欧洲天堂 | 色噜噜视频影院 | 网站在线观看 | 字幕网yellow 91在线 | 求av网址 | 麻豆高清视频在线观看 | 亚洲国产视频在线观看 | 欧美理论在线 | 亚洲国产成人精品青青草原100 | 成人高清毛片a | 欧美激情亚洲激情 | 欧美xxxxhd | 国产日韩高清一区二区三区 | 最近中文字幕mv在线高清 | 伊人久久伊人 | 午夜国产精品不卡在线观看 | 波多野结衣与老人公gvg在线 | jizz性欧美12 | 国产日本欧美在线观看乱码 | 亚洲美女一区 | 波多野结衣免费观看视频 | 亚洲网站视频在线观看 | 亚洲欧美韩国日本 | 久草免费小视频 | 12306播播影视播播影院午夜 | 成人影院一区二区三区 | 一级毛片不卡免费看老司机 | 国产成+人欧美+综合在线观看 | 毛片在线不卡 | 亚洲综合二区 | 国产福利在线观看永久免费 | 亚洲另类精品xxxx人妖 | 亚洲一本视频 | 欧美视频精品在线 | 欧美国产成人精品一区二区三区 | 亚洲 欧美 精品 中文第三 | 色视频一区二区三区 | 欧美成人午夜做爰视频在线观看 | 欧美 video | 最新avhd101在线播放 |